<div class="b_readbook_txt"><h3 class="book"> Linux<br> программирование в примерах<br></h3> <a name="t1"></a><h3 class="book">Предисловие<br> </h3> <p class="book">Одним из лучших способов научиться программированию является чтение хорошо написанных программ. Данная книга обучает фундаментальному API системных вызовов Linux — тех, которые образуют ядро любой значительной программы — представляя код из программных изделий, которые вы используете каждый день.</p> <p class="book">Рассматривая конкретные программы, вы не только видите, как использовать Linux API, но можете также исследовать реальные проблемы (производительности, переносимости, устойчивости), которые возникают при написании программного обеспечения.</p> <p class="book">Хотя книга называется <i>Программирование под Linux на примерах</i>, все, что мы рассматриваем, относится также к современным системам Unix, если не отмечено противное. Обычно мы используем термин «Linux» для обозначения ядра Linux, a «GNU/Linux» для обозначения всей системы (ядра, библиотек, инструментов). Часто также мы говорим «Linux», когда имеем в виду и Linux, GNU/Linux и Unix; если что-то является специфичным для той или иной системы, мы отмечаем это явным образом.</p> <h5 class="book">Аудитория</h5> <p class="book">Данная книга предназначена для лиц, разбирающихся в программировании и знакомых с основами С, по крайней мере на уровне книги Кернигана и Ричи <i>Программирование на языке С</i>. (Программисты Java, желающие прочесть эту книгу, должны разбираться в указателях С, поскольку С активно их использует.) В примерах используются как версия Стандартного С 1990 года, так и Оригинальный С.</p> <p class="book">В частности, вам следует быть знакомыми со всеми операторами С, структурами управления потоком исполнения, использованием объявлений переменных и указателей, функциями работы со строками, использованием <code>exit()</code> и набором функций <code>&lt;stdio.h&gt;</code> для файлового ввода/вывода.</p> <p class="book">Вы должны понимать базовые концепции <i>стандартного ввода</i>, <i>стандартного вывода</i> и <i>стандартной ошибки</i>, а также знать тот факт, что все программы на С получают массив символьных строк, представляющих вызываемые опции и аргументы. Вы должны также быть знакомы с основными инструментами командной строки, такими, как <code>cd</code>, <code>cp</code>, <code>date</code>, <code>ln</code>, <code>ls</code>, <code>man</code> (и <code>info</code>, если он у вас имеется), <code>rmdir</code> и <code>rm</code>, с использованием длинных и коротких опций командной строки, переменных окружения и перенаправления ввода/вывода, включая каналы.</p> <p class="book">Мы предполагаем, что вы хотите писать программы, которые работают не только под GNU/Linux, но и на множестве различных систем Unix. С этой целью мы помечаем каждый интерфейс с точки зрения его доступности (лишь для систем GLIBC или определен в POSIX и т.д.), а в тексте приведены также советы по переносимости.</p> <p class="book">Программирование, которое здесь приводится, может быть на более низком уровне, чем вы обычно использовали; это нормально. Системные вызовы являются основными строительными блоками для операций более высокого уровня и поэтому они низкоуровневые по своей природе. Это, в свою очередь, определяет использование нами С: функции API были спроектированы для использования из С, и код, связывающий их с языками более высокого уровня, такими как C++ и Java, неизбежно будет на более низком уровне и вероятнее всего, написанным на С. «Низкий уровень» не означает «плохой», это просто значит «более стимулирующий».</p> <h5 class="book">Что вы изучите</h5> <p class="book">Данная книга фокусируется на базовых API, образующих ядро программирования под Linux:</p> <p class="book">•&nbsp;Управление памятью</p> <p class="book">•&nbsp;Файловый ввод/вывод</p> <p class="book">•&nbsp;Метаданные файлов</p> <p class="book">•&nbsp;Процессы и сигналы</p> <p class="book">•&nbsp;Пользователи и группы</p> <p class="book">•&nbsp;Поддержка программирования (сортировка, анализ аргументов и т.д.)</p> <p class="book">•&nbsp;Интернационализация</p> <p class="book">•&nbsp;Отладка</p> <p class="book">Мы намеренно сохранили список тем коротким. Мы считаем, что попытка научить а одной книге «всему, что можно узнать», пугает. Большинство читателей предпочитают книжки поменьше, более сфокусированные, и лучшие книги по Unix написаны таким способом</p> <p class="book">Поэтому вместо одного гигантского тома мы планируем несколько книг: одну по межпроцессному взаимодействию (IPC) и сетям, другую по разработке программного обеспечения и переносимости кода. Мы также положили глаз а направлении дополнительных томов в серии <i>Программирование под Linux на примерах</i>, которые будут раскрывать такие темы, как многопоточное программирование и программирование графических интерфейсов пользователя (GUI).</p> <p class="book">Рассматриваемые нами API включают как системные вызовы, так и библиотечные функции. Действительно, на уровне С оба вида выступают в виде простых вызовов функций. <i>Системный вызов</i> является непосредственным запросом системной службы, такой, как чтение или запись файла или создание процесса. <i>Библиотечная функция</i>, с другой стороны, работает на уровне пользователя, возможно, никогда не запрашивая какие-либо сервисы у операционной системы. Системные вызовы документированы в разделе 2 справочного руководства (которое можно просмотреть с помощью команды man), а библиотечные функции документированы в разделе&nbsp;3.</p> <p class="book">Нашей целью является научить вас использовать Linux API на примерах: в частности, посредством использования, где это возможно, как оригинальных исходных кодов Unix, так и инструментов GNU. К сожалению, самодостаточных примеров не так много, как должно было бы быть. Поэтому мы также написали большое число небольших демонстрационных программ. Был сделан акцент на принципах программирования: особенно на таких аспектах программирования для GNU, как «никаких произвольных ограничений», которые превращают инструменты GNU в незаурядные программы.</p> <p class="book">Выбор для изучения повседневных программ намеренный. Если вы уже использовали GNU/Linux в течение какого-либо периода времени, вы уже понимаете, что делают такие программы, как <code>ls</code> и <code>cp</code>; после этого просто погрузиться прямо в то, как работают программы, не тратя много времени на изучение того, что они делают.</p> <p class="book">Иногда мы представляем как высокоуровневый, так и низкоуровневый способы выполнения задачи. Обычно стандарт интерфейса более высокого уровня реализуется посредством более низкоуровневого интерфейса или конструкции. Мы надеемся, что такой взгляд на то, что происходит «под капотом», поможет вам понять, как это работает; для всего кода, который вы пишете сами, нужно всегда использовать более высокоуровневый, стандартный интерфейс.</p> <p class="book">Таким же образом иногда мы представляем функции, которые предоставляют определенные возможности, а затем рекомендуем (по указанной причине) избегать этих функций! Главной причиной такого подхода является то, что вы получаете возможность узнавать эти функции при встрече и понимать код с их использованием. Всеобъемлющее знание темы требует понимания не только того, что вы можете сделать, но и того, что должны или не должны делать.</p> <p class="book">Наконец, каждая глава завершается упражнениями. Некоторые из них требуют модификации или написания кода. Другие больше относятся к категориям «Мысленных экспериментов» или «как вы думаете, почему…». Мы рекомендуем выполнить их все&nbsp;— они помогут закрепить понимание материала.</p> <h5 class="book">Небольшой — значит красивый: программы Unix</h5> <blockquote class="book"><p class="book">Закон Хоара: «Внутри каждой большой программы есть старающаяся пробиться маленькая программа»</p><blockquote class="book"><i>- C.A.R. Hoare -</i></blockquote> </blockquote> <p class="book">Вначале мы планировали обучать Linux API, используя код инструментов GNU. Однако, современные версии даже простых программ командной строки (подобно <code>mv</code> и <code>cp</code>) большие и многофункциональные. Это особенно верно в отношении GNU вариантов стандартных утилит, которые допускают длинные и короткие опции, делают все, требуемое POSIX и часто имеют также дополнительные, внешне не связанные опции (подобно выделению вывода).</p> <p class="book">Поэтому возник разумный вопрос: «Как мы можем в этом большом и запутывающем лесу сконцентрироваться на одном или двух важных деревьях?» Другими словами, если мы представим современные полнофункциональные программы, будет ли возможно увидеть лежащую в основе работу программы?</p> <p class="book">Вот когда <i>закон Хоара</i><sup><a name="anotelink1" id="anotelink1" href="#n1" title=" Это знаменитое высказывание было сделано на Международном симпозиуме по эффективному производству больших программ в Jablonna, Польша, 10-14 августа 1970&nbsp;г. — Примеч. автора.">[1]</a></sup> вдохновил нас на рассмотрение в качестве примера кода оригинальных программ Unix. Оригинальные утилиты V7 Unix маленькие и простые, что упрощает наблюдение происходящего и понимание использования системных вызовов (V7 был выпущен около 1979 г.; это общий предок всех современных систем Unix, включая системы GNU/Linux и BSD.)</p> <p class="book">В течение многих лет исходный код Unix был защищен авторскими правами и лицензионными соглашениями коммерческой тайны, что затрудняло его использование для обучения и делало невозможным опубликование. Это до сих пор верно в отношении исходного кода всех коммерческих систем Unix. Однако в 2002 г. Caldera (в настоящее время работающая под именем SCO) сделала оригинальный код Unix (вплоть до V7 и 32V Unix) доступным на условиях лицензии в стиле Open Source (см. приложение В «Лицензия Caldera для старой Unix»). Это дает нам возможность включить в эту книгу код из ранних систем Unix.</p> <h5 class="book">Стандарты</h5> <p class="book">По всей книге мы ссылаемся на несколько различных официальных стандартов. Стандарт является документом, описывающим, как что-либо работает. Официальные стандарты существуют для многих вещей, например, форма, размещение и назначение отверстий в электрической розетке на вашей стене определяется официальным стандартом, так что все сетевые шнуры в вашей стране работают со всеми розетками.</p> <p class="book">Таким же образом официальные стандарты для вычислительных систем определяют, как они должны работать; это дает возможность пользователям и разработчикам знать, чего ожидать от своего программного обеспечения, и дает им возможность жаловаться своему поставщику, когда программное обеспечение не работает.</p> <p class="book">Здесь интерес для нас представляют:</p> <p class="book">1.&nbsp;<i>ISO/IEC International Standard 9899 Programming Languages — С (Международный стандарт ISO/IEC 9899. Языки программирования - С), 1990</i>. Первый официальный стандарт для языка программирования С.</p> <p class="book">2. <i>ISO/IEC International Standard 9899. Programming Languages — С, Second edition, 1999 (Международный стандарт ISO/IEC 9899. Языки программирования С, второе издание)</i>. Второй (текущий) официальный стандарт для языка программирования C.</p> <p class="book">3.&nbsp;<i>ISO/IEC International Standard 14882. Programming Languages — С++, 1998 (Международный стандарт ISO/IEC 14882. Языки программирования - С++)</i>. Первый официальный стандарт для языка программирования С++.</p> <p class="book">4.&nbsp;<i>ISO/IEC International Standard 14882. Programming Languages — С++, 2003 (Международный стандарт 14882. Языки программирования&nbsp;— С++)</i>. Второй (текущий) официальный стандарт для языка программирования С++.</p> <p class="book">5.&nbsp;<i>IEEE Standard 1003 1-2001 Standard for Information Technology — Portable Operating System Interface (POSIX®) (Стандарт IEEE 1003.1-2001. Стандарт информационных технологий — переносимый интерфейс операционной системы)</i>. Текущая версия стандарта POSIX; описывает поведение, ожидаемое от Unix и Unix-подобных систем. Данное издание освещает как системные вызовы, так и библиотечные интерфейсы с точки зрения программиста C/C++, и интерфейс оболочки и инструментов с точки зрения пользователя. Он состоит из нескольких томов:</p> <p class="book">&nbsp;•&nbsp;<i>Базовые определения (Base Definitions)</i>. Определения терминов, средств и заголовочных файлов.</p> <p class="book">&nbsp;•&nbsp;<i>Базовые определения — Обоснование (Base Definitions — Rationale)</i>. Объяснения и обоснования выбора средств как включенных, так и невключенных в стандарт.</p> <p class="book">&nbsp;•&nbsp;<i>Системные интерфейсы (System Interfaces)</i>. Системные вызовы и библиотечные функции. POSIX называет обе разновидности просто «функции».</p> <p class="book">&nbsp;•&nbsp;<i>Оболочка и инструменты (Shell and Utilities)</i>. Язык оболочки и доступные для интерактивного использования и использования сценариями оболочки инструменты.</p> <p class="book">Хотя стандарты языков не являются захватывающим чтением, можно рассмотреть покупку экземпляра стандарта С, он дает окончательное определение языка. Книги можно приобрести в ANSI<sup><a name="anotelink2" id="anotelink2" href="#n2" title=" http://www.ansi.org — Примеч. автора.">[2]</a></sup> и в ISO<sup><a name="anotelink3" id="anotelink3" href="#n3" title=" http://www.iso.ch — Примеч. автора.">[3]</a></sup>. (PDF-версия стандарта С вполне доступна.)</p> <p class="book">Стандарт POSIX можно заказать в The Open Group<sup><a name="anotelink4" id="anotelink4" href="#n4" title=" http://www.opengroup.org — Примеч. автора.">[4]</a></sup>. Исследуя в каталоге их изданий элементы, перечисленные а «Спецификациях CAE» («CAE Specifications»), вы можете найти отдельные страницы для каждой части стандарта (озаглавленные с «C031» по «C034»). Каждая такая страница предоставляет свободный доступ к HTML версии определенного тома</p> <p class="book">Стандарт POSIX предназначен для реализации как Unix и Unix-подобных систем, так и не-Unix систем. Таким образом, базовые возможности, которые он предоставляет, составляют лишь часть возможностей, которые есть на системах Unix. Однако, стандарт POSIX определяет также <i>расширения</i> — дополнительные возможности, например, для многопоточности или поддержки реального времени. Для нас важнее всего расширение <i>X/Open System Interface</i> (XSI), описывающее возможности исторических систем Unix.</p> <p class="book">По всей книге мы помечаем каждый API в отношении его доступности: ISO С, POSIX, XSI, только GLIBC или как нестандартный, но широко доступный.</p> <h5 class="book">Возможности и мощь: программы GNU</h5> <p class="book">Ограничив себя лишь оригинальным кодом Unix, можно было бы получить интересную историческую книгу, но она была бы не очень полезна в XXI веке. Современные программы не имеют тех же ограничений (памяти, мощности процессора, дискового пространства и скорости), которые были у ранних систем Unix. Более того, они должны работать в многоязычном мире — ASCII и американского английского недостаточно.</p> <p class="book">Что еще важнее, одной из главных свобод, выдвинутых явным образом Фондом бесплатных программ (Free Software Foundation) и проектом GNU<sup><a name="anotelink5" id="anotelink5" href="#n5" title=" http://www.gnu.org — Примеч. автора.">[5]</a></sup>, является «свобода обучения». Программы GNU предназначены для обеспечения большого собрания хорошо написанных программ, которые программисты среднего уровня могут использовать а качестве источника для своего обучения.</p> <p class="book">Используя программы GNU, мы преследуем две цели: показать вам хорошо написанный современный код, на котором вы будете учиться писать хорошие программы, а также использовать API.</p> <p class="book">Мы считаем, что программное обеспечение GNU лучше, поскольку оно свободно (в смысле «свободы», а не «бесплатного пива»)<sup><a name="anotelink6" id="anotelink6" href="#n6" title=" Игра слов: free — свободный, бесплатный — Примеч. перев.">[6]</a></sup>. Но признается также, что программное обеспечение GNU часто также <i>технически</i> лучше соответствующих двойников в Unix, и мы уделили место в разделе 1.4 «Почему программы GNU лучше», чтобы это объяснить</p> <p class="book">Часть примеров кода GNU происходит из <code>gawk</code> (GNU <code>awk</code>). Главной причиной этого является то, что это программа, с которой мы очень знакомы, поэтому было просто отобрать оттуда примеры. У нас нет относительно нее других притязаний.</p> <h5 class="book">Обзор глав</h5> <p class="book">Вождение автомобиля целостный процесс, включающий множество одновременных задач. Во многих отношениях программирование по Linux похоже на это, требуя понимания многих аспектов API, таких, как файловый ввод/вывод, метаданные файлов, каталоги, хранение сведений о времени и т.д.</p> <p class="book">В первой части книги рассматривается достаточное количество этих отдельных элементов, чтобы дать возможность изучить первую значительную программу, V7 ls. Затем мы завершим наше обсуждение файлов и пользователей, рассмотрев иерархии файлов и принципы работы файловых систем и их использование.</p> <p class="book"><i>Глава 1, «Введение»,</i></p> <p class="book">описывает модели файлов и процессов Unix и Linux, рассматривает отличия оригинального С от стандартного С 1990 г., а также предоставляет обзор принципов, которые делают программы GNU в целом лучшими по сравнению со стандартными программами Unix.</p> <p class="book"><i>Глава 2, «Аргументы, опции и переменные окружения»,</i></p> <p class="book">описывает, как С программа получает доступ к аргументам и опциям командной строки и обрабатывает их, а также объясняет, как работать с переменными окружения.</p> <p class="book"><i>Глава 3, «Управление памятью на уровне пользователя»,</i></p> <p class="book">предоставляет обзор различных видов используемой памяти, доступной работающему процессу. Управление памятью на уровне пользователя является основным для каждого нетривиального приложения, поэтому важно понять это с самого начала.</p> <p class="book"><i>Глава 4, «Файлы и файловый ввод/вывод»,</i></p> <p class="book">обсуждает базовый файловый ввод/вывод, показывая, как создавать и использовать файлы. Понимание этого важно для всего последующего материала.</p> <p class="book"><i>Глава 5, «Каталоги и служебные данные файлов»,</i></p> <p class="book">описывает, как работают каталоги, прямые и символические ссылки. Затем описываются служебные данные файлов, такие, как владелец, права доступа и т.д., а также освещается работа с каталогами</p> <p class="book"><i>Глава 6, «Общие библиотечные интерфейсы — часть 1»,</i></p> <p class="book">рассматривает первый набор интерфейсов общего программирования, которые будут нам нужны для эффективного использования служебных данных файлов.</p> <p class="book"><i>Глава 7, «Соединяя все вместе:</i> <code><i>ls</i></code><i>»,</i></p> <p class="book">связывает воедино все рассмотренное до сих пор, рассматривая программу V7 <code>ls</code>.</p> <p class="book"><i>Глава 8, «Файловые системы и обходы каталогов»,</i></p> <p class="book">описывает, как монтируются и демонтируются файловые системы и как программа может получить сведения о том, что смонтировано в системе. В главе описывается также, как программа может легко «обойти» всю иерархию файлов, предпринимая а отношении каждого встреченного объекта необходимые действия.</p> <p class="book">Вторая часть книги имеет дело с созданием и управлением процессами, межпроцессным взаимодействием посредством каналов и сигнала, ID пользователей и групп и дополнительными интерфейсами общего программирования. Далее в книге сначала описываются интернационализация с использованием GNU <code>gettext</code>, а затем несколько расширенных API.</p> <p class="book"><i>Глава 9, «Управление процессами и каналы»,</i></p> <p class="book">рассматривает создание процесса, исполнение программы, межпроцессное взаимодействие (IPC) с использованием каналов и управление дескрипторами файлов, включая неблокирующий ввод/вывод.</p> <p class="book"><i>Глава 10, «Сигналы»,</i></p> <p class="book">обсуждает сигналы, упрощенную форму межпроцессного взаимодействия. Сигналы играют также важную роль а управлении родительским процессом порожденных процессов.</p> <p class="book"><i>Глава 11, «Права доступа и ID пользователей и групп»,</i></p> <p class="book">рассматривает, как идентифицируются процессы и файлы, как работает проверка прав доступа и как работают механизмы <code>setuid</code> и <code>setgid</code>.</p> <p class="book"><i>Глава 12, «Общие библиотечные интерфейсы — часть 2»,</i></p> <p class="book">рассматривает оставшуюся часть общих API; многие из них более специализированы, чем первый общий набор API.</p> <p class="book"><i>Глава 13, «Интернационализация и локализация»,</i></p> <p class="book">объясняет, как почти без усилий обеспечить работу ваших программ на нескольких языках.</p> <p class="book"><i>Глава 14, «Расширенные интерфейсы»,</i></p> <p class="book">описывает несколько расширенных версий интерфейсов, освещенных в предыдущих главах, а также более подробно освещает блокировку файлов.</p> <p class="book">Мы завершаем книгу главой об отладке, поскольку (почти) никто не может сделать все правильно с первого раза, и рекомендуем финальный проект для закрепления ваших знаний по рассмотренным в данной книге API.</p> <p class="book"><i>Глава 15, «Отладка»,</i></p> <p class="book">описывает основы отладчика GDB, передает как можно больше нашего опыта в этой области и рассматривает несколько полезных инструментов для осуществления различных видов отладки.</p> <p class="book"><i>Глава 16, «Проект, связывающий все воедино»,</i></p> <p class="book">представляет значительный проект по программированию, который использует почти все, рассмотренное в книге.</p> <p class="book">Несколько приложений освещают представляющие интерес темы, включая лицензии для использованного в данной книге исходного коде.</p> <p class="book"><i>Приложение А, «Научитесь программированию за десять лет»,</i></p> <p class="book">ссылается на знаменитое высказывание: «Москва не сразу строилась»<sup><a name="anotelink7" id="anotelink7" href="#n7" title=" В оригинале: «Rome wasn't built in a day» — Примеч. перев.">[7]</a></sup>. Также и квалификация в Linux/Unix и понимание этих систем приходит лишь со временем и практикой. С этой целью мы включили это эссе Петера Норвига, которое мы горячо рекомендуем.</p> <p class="book"><i>Приложение В, «Лицензия Caldera для старой Unix»,</i></p> <p class="book">охватывает исходный код Unix, использованный в данной книге.</p> <p class="book"><i>Приложение С, «Общедоступная лицензия GNU»,</i></p> <p class="book">охватывает исходный код GNU, использованный в данной книге.</p> <h5 class="book">Соглашения об обозначениях</h5> <p class="book">Как и в любой книге на компьютерную тему, для передачи информации мы используем определенные соглашения об обозначениях. <i>Определения</i> или первое использование термина выделяется курсивом, как слово «Определения» в начале этого предложения. Курсив используется также для <i>выделения</i>, для цитирования других работ и комментариев в примерах. Переменные элементы, такие, как аргументы или имена файлов, выглядят <b>таким образом</b>. Иногда мы используем жирный шрифт, когда нужно <b>усилить</b> мысль.</p> <p class="book">Вещи, находящиеся на компьютере, обозначаются моноширинными шрифтом, как в случае имен файлов (<code>foo.c</code>) и названий команд (<code>ls</code>, <code>grep</code>). Короткие фрагменты, которые вы вводите, дополнительно заключаются в одинарные кавычки: '<code>ls -l *.с</code>'</p> <p class="book"><code>$</code> и <code>&gt;</code> являются первичным и вторичным приглашениями оболочки Борна и используются при отображении интерактивных примеров. Ввод пользователя выделяется другим шрифтом от обычного вывода компьютера в примерах. Примеры выглядят следующим образом:</p> <p class="book"><code>$ <b>ls -1</b> /* Просмотр файлов. Опция - цифра 1, а не буква l */</code></p> <p class="book"><code>foo</code></p> <p class="book"><code>bar</code></p> <p class="book"><code>baz</code></p> <p class="book">Мы предпочитаем оболочку Борна и ее варианты (ksh93, Bash) по сравнению с оболочкой С; соответственно на всех наших примерах показана лишь оболочка Борна. Знайте, что правила применения кавычек и переноса на следующую строку в оболочке С другие; если вы используете ее, то на свой страх и риск!<sup><a name="anotelink8" id="anotelink8" href="#n8" title=" См. справочные страницы csh(1) и tcsh(1) и книгу Using csh &amp; tcsh, by Paul DuBois, O'Reilly &amp; Associates. Sebastopol, CA. USA, 1995. ISBN 1-56592-132-1 — Примеч. автора.">[8]</a></sup></p> <p class="book">При ссылках на функции в программах мы добавляем к имени функции пустую пару скобок: <code>printf()</code>, <code>strcpy()</code>. При ссылке на справочную страницу (доступную по команде man), мы следуем стандартному соглашению Unix по написанию имени команды или функции курсивом, а раздела — в скобках после имени обычным шрифтом: <i>awk</i>(1), <i>printf</i>(3)<sup><a name="anotelink9" id="anotelink9" href="#n9" title=" Соответствующие справочные страницы можно посмотреть с помощью man 1 awk и man 3 printf — Примеч. науч. ред.">[9]</a></sup>.</p> <h5 class="book">Где получить исходные коды Unix и GNU</h5> <p class="book">Вы можете захотеть получить копни программ, которые мы использовали в данной книге, для своих собственных экспериментов и просмотра. Весь исходный код доступен через Интернет, а ваш дистрибутив GNU/Linux содержит исходный код для инструментов GNU.</p> <h5 class="book">Код Unix</h5> <p class="book">Архивы различных «древних» версий Unix поддерживаются Обществом наследства UNIX (The UNIX Heritage Society — TUHS), <code>http://www.tuhs.org</code>.</p> <p class="book">Наибольший интерес представляет возможность просматривать архив старых исходных кодов Unix через веб. Начните с <code>http://minnie.tuhs.org/UnixTree/</code>. Все примеры кода в данной книге из седьмого издания исследовательской системы UNIX, известной также как «V7».</p> <p class="book">Сайт TUHS физически расположен в Австралии, хотя имеются зеркала архива по всему миру — см. <code>http://www.tuhs.org/archive_sites.html</code>. Эта страница также указывает, что архив доступен для зеркала через <code>rsync</code>. (Если у вас нет <code>rsync</code>, см. <code>http://rsync.samba.org/</code>: это стандартная утилита на системах GNU/Linux.)</p> <p class="book">Чтобы скопировать весь архив, потребуется примерно 2-3 гигабайта дискового пространства. Для копирования архива создайте пустой каталог, а в нем выполните следующие команды:</p> <p class="book"><code>mkdir Applications 4BSD PDP-11 PDP-11/Trees VAX Other</code></p> <p class="book"><code>rsync -avz minnie.tuhs.org::UA_Root .</code></p> <p class="book"><code>rsync -avz minnie.tuhs.org::UA_Applications Applications</code></p> <p class="book"><code>rsync -avz minnie.tuhs.org::UA_4BSD 4BSD</code></p> <p class="book"><code>rsync -avz minnie.tuhs.org::UA_PDP11 PDP-11</code></p> <p class="book"><code>rsync -avz minnie.tuhs.org::UA_PDP11_Trees PDP-11/Trees</code></p> <p class="book"><code>rsync -avz minnie.tuhs.org::UA_VAX VAX</code></p> <p class="book"><code>rsync -avz minnie.tuhs.org::UA_Other Other</code></p> <p class="book">Вы можете пропустить копирование каталога <code>Trees</code>, который содержит извлечения из нескольких версий Unix и занимает на диске около 700 мегабайт.</p> <p class="book">В рассылке TUHS можно также поинтересоваться, нет ли поблизости от вас кого-нибудь, кто мог бы предоставить вам архив на CD-ROM, чтобы избежать пересылки по Интернету такого большого количества данных.</p> <p class="book">Группа в Southern Storm Software, Pty. Ltd. в Австралии «модернизировала» часть кода уровня пользователя V7, так что его можно откомпилировать и запустить на современных системах, особенно на GNU/Linux. Этот код можно загрузить с их веб-сайта<sup><a name="anotelink10" id="anotelink10" href="#n10" title=" http://www.southern-storm.com.au/v7upgrade.html — Примеч. автора.">[10]</a></sup>.</p> <p class="book">Интересно отметить, что код V7 не содержит в себе каких-либо уведомлений об авторских правах или разрешениях. Авторы писали код главным образом для себя и своего исследования, оставив проблемы разрешений отделу корпоративного лицензирования AT&amp;T.</p> <h5 class="book">Код GNU</h5> <p class="book">Если вы используете GNU/Linux, ваш дистрибутив поступит с исходным кодом, предположительно в формате, используемом для упаковки (файлы RPM Red Hat, файлы DEB Debian, файлы <code>.tar.gz</code> Slackware и т.д.) Многие примеры в книге взяты из GNU Coreutils, версия 5.0. Найдите соответствующий CD-ROM для своего дистрибутива GNU/Linux и используйте для извлечения кода соответствующий инструмент. Или следуйте для получения кода инструкциям в следующих нескольких абзацах.</p> <p class="book">Если вы предпочитаете самостоятельно получать файлы из ftp-сайта GNU, вы найдете его по адресу: <code>ftp://ftp.gnu.org/gnu/coreutils/coreutils-5.0.tar.gz</code>.</p> <p class="book">Для получения файла можно использовать утилиту <code>wget</code>:</p> <p class="book"><code>$ <b>wget ftp://ftp.gnu.org/ena/coreutils/coreuitils-5.0.tar.gz</b></code></p> <p class="book"><code>/* Получить дистрибутив */</code></p> <p class="book"><code>/* ... здесь при получении файла куча вывода ... */</code></p> <p class="book">В качестве альтернативы можно использовать для получения файла старый добрый <code>ftp</code>:</p> <p class="book"><code>$ <b>ftp ftp.gnu.org</b> /* Подключиться к ftp-сайту GNU */</code></p> <p class="book"><code>Connected to ftp.gnu.org (199.232.41.7).</code></p> <p class="book"><code>220 GNU FTP server ready.</code></p> <p class="book"><code>Name (ftp.gnu.org:arnold): <b>anonymous</b> /* Использовать анонимный ftp */</code></p> <p class="book"><code>331 Please specify the password.</code></p> <p class="book"><code>Password: /* Пароль на экране не отображается */</code></p> <p class="book"><code>230-If you have any problems with the GNU software or its downloading,</code></p> <p class="book"><code>230-please refer your questions to &lt;gnu@gnu.org&gt;.</code></p> <p class="book"><code>... /* Куча вывода опущена */</code></p> <p class="book"><code>230 Login successful. Have fun.</code></p> <p class="book"><code>Remote system type is UNIX.</code></p> <p class="book"><code>Using binary mode to transfer files.</code></p> <p class="book"><code>ftp&gt; <b>cd /gnu/coreutils</b> /* Перейти в каталог Coreutils */</code></p> <p class="book"><code>250 Directory successfully changed.</code></p> <p class="book"><code>ftp&gt; <b>bin</b></code></p> <p class="book"><code>200 Switching to Binary mode.</code></p> <p class="book"><code>ftp&gt; <b>hash</b> /* Выводить символы # по мере закачки */</code></p> <p class="book"><code>Hash mark printing on (1024 bytes/hash mark).</code></p> <p class="book"><code>ftp&gt; <b>get coreutils-5.0.tar.gz</b> /* Retrieve file */</code></p> <p class="book"><code>local: coreutils-5.0.tar.gz</code></p> <p class="book"><code>remote: coreutils-5.0.tar.gz</code></p> <p class="book"><code>227 Entering Passive Mode (199,232,41,7,86,107)</code></p> <p class="book"><code>150 Opening BINARY mode data connection for coreutils-5.0.tar.gz (6020616 bytes)</code></p> <p class="book"><code>######################################################################</code></p> <p class="book"><code>######################################################################</code></p> <p class="book"><code>...</code></p> <p class="book"><code>226 File send OK.</code></p> <p class="book"><code>6020616 bytes received in 2.03e+03 secs (2.9 Kbytes/sec)</code></p> <p class="book"><code>ftp&gt; <b>quit</b> /* Закончить работу */</code></p> <p class="book"><code>221 Goodbye.</code></p> <p class="book">Получив файл, извлеките его следующим образом:</p> <p class="book"><code>$ <b>gzip -dc &lt; coreutils-5.0.tar.gz | tar -xvpf -</b> /* Извлечь файлы */</code></p> <p class="book"><code>/* ... при извлечении файла куча вывода ... */</code></p> <p class="book">Системы, использующие GNU tar, могут использовать следующее заклинание:</p> <p class="book"><code>$ <b>tar -xvpzf coreutils-5.0.tar.gz</b> /* Извлечь файлы */</code></p> <p class="book"><code>/* ... при извлечении файла куча вывода ... */</code></p> <p class="book">В соответствии с общедоступной лицензией GNU, вот сведения об авторских правах для всех GNU программ, процитированных в данной книге. Все программы являются «свободным программным обеспечением; вы можете распространять их и/или модифицировать на условиях общедоступной лицензии GNU в изданном Фондом бесплатных программ виде; либо версии 2 лицензии, либо (по вашему выбору) любой последующей версии». Текст общедоступной лицензии GNU см. в приложении С «Общедоступная лицензия GNU».</p> <table><tbody><tr align="left"> <th align="left" valign="top">Файл Coreutils 5.0</th> <th align="left" valign="top">Даты авторского права</th></tr><tr align="left"> <td align="left" valign="top"><code>lib/safe-read.с</code></td> <td align="left" valign="top">© 1993-1994, 1998, 2002</td></tr><tr align="left"> <td align="left" valign="top"><code>lib/safe-write.c</code></td> <td align="left" valign="top">© 2002</td></tr><tr align="left"> <td align="left" valign="top"><code>lib/utime.c</code></td> <td align="left" valign="top">© 1998, 2001-2002</td></tr><tr align="left"> <td align="left" valign="top"><code>lib/xreadlink.с</code></td> <td align="left" valign="top">© 2001</td></tr><tr align="left"> <td align="left" valign="top"><code>src/du.c</code></td> <td align="left" valign="top">© 1988-1991, 1995-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>src/env.с</code></td> <td align="left" valign="top">© 1986, 1991-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>src/install.с</code></td> <td align="left" valign="top">© 1989-1991, 1995-2002</td></tr><tr align="left"> <td align="left" valign="top"><code>src/link.c</code></td> <td align="left" valign="top">© 2001-2002</td></tr><tr align="left"> <td align="left" valign="top"><code>src/ls.с</code></td> <td align="left" valign="top">© 1985, 1988, 1990, 1991, 1995-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>src/pathchk.c</code></td> <td align="left" valign="top">© 1991-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>src/sort.с</code></td> <td align="left" valign="top">© 1988, 1991-2002</td></tr><tr align="left"> <td align="left" valign="top"><code>src/sys2.h</code></td> <td align="left" valign="top">© 1997-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>src/wc.с</code></td> <td align="left" valign="top">© 1985, 1991, 1995-2002</td></tr><tr align="left"> <td align="left" valign="top">Файл Gawk 3.0.6</td> <td align="left" valign="top">Даты авторского права</td></tr><tr align="left"> <td align="left" valign="top"><code>eval.с</code></td> <td align="left" valign="top">© 1986, 1988, 1989, 1991-2000</td></tr><tr align="left"> <td align="left" valign="top">Файл Gawk 3.1.3</td> <td align="left" valign="top">Даты авторского права</td></tr><tr align="left"> <td align="left" valign="top"><code>awk.h</code></td> <td align="left" valign="top">© 1986, 1988, 1989, 1991-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>builtin.с</code></td> <td align="left" valign="top">© 1986, 1988, 1989, 1991-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>eval.с</code></td> <td align="left" valign="top">© 1986, 1988, 1989, 1991-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>io.c</code></td> <td align="left" valign="top">© 1986, 1988, 1989, 1991-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>main.с</code></td> <td align="left" valign="top">© 1986, 1988, 1989, 1991-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>posix/gawkmisc.с</code></td> <td align="left" valign="top">© 1986, 1988, 1989, 1991-1998, 2001-2003</td></tr><tr align="left"> <td align="left" valign="top">Файл Gawk 3.1.4</td> <td align="left" valign="top">Даты авторского права</td></tr><tr align="left"> <td align="left" valign="top"><code>builtin.c</code></td> <td align="left" valign="top">© 1986, 1988, 1989, 1991-2004</td></tr><tr align="left"> <td align="left" valign="top">Файл GLIBC 23.2</td> <td align="left" valign="top">Даты авторского права</td></tr><tr align="left"> <td align="left" valign="top"><code>locale/locale.h</code></td> <td align="left" valign="top">© 1991, 1992, 1995-2002</td></tr><tr align="left"> <td align="left" valign="top"><code>posix/unistd.h</code></td> <td align="left" valign="top">© 1991-2003</td></tr><tr align="left"> <td align="left" valign="top"><code>time/sys/time.h</code></td> <td align="left" valign="top">© 1991-1994, 1996-2003</td></tr><tr align="left"> <td align="left" valign="top">Файл Make 3.80</td> <td align="left" valign="top">Даты авторского права</td></tr><tr align="left"> <td align="left" valign="top"><code>read.с</code></td> <td align="left" valign="top">© 1988-1997, 2002</td></tr> </tbody></table> <h5 class="book">Где получить примеры программ, использованные в данной книге</h5> <p class="book">Примеры программ, использованные в данной книге, можно найти по адресу: <code>http://authors.phptr.com/robbins</code>.</p> <h5 class="book">Об обложке</h5> <blockquote class="book"><p class="book">«Это оружие Джедая …, элегантное оружие для более цивилизованной эпохи. На протяжении тысяч поколений Рыцари Джедай были защитниками мира и справедливости в Старой Республике. От мрачных времен, до Империи».</p><blockquote class="book"><i>- Оби-Ван Кеноби -</i></blockquote> </blockquote> <p class="book">Возможно, вы удивляетесь, почему мы поместили на обложке легкую саблю и использовали ее во внутреннем оформлении книги. Что она представляет и какое она имеет отношение к программированию под Linux?</p> <p class="book">В руках Рыцаря Джедай легкая сабля является и мощным оружием, и предметом красоты. Ее использование демонстрирует мощь, знание, контроль над Силой и напряженное обучение для владеющего им Джедая.</p> <p class="book">Элегантность легкой сабли отражает элегантность оригинального дизайна Unix API. Там также обдуманное, точное использование API и программных инструментов и принципов проектирования GNU привело к сегодняшним мощным, гибким, развитым системам GNU/Linux. Эта система демонстрирует знание и понимание программистов, создавших все их компоненты.</p> <p class="book">И конечно, легкие сабли — это просто круто!</p> <h5 class="book">Благодарности</h5> <p class="book">Написание книги требует большого количества работы, а чтобы сделать это хорошо, нужна помощь от многих людей. Д-р Brian W. Kernighan, д-р Doug McIlroy, Peter Memishian и Peter van der Linden сделали рецензию первоначального предложения. David J. Agans, Fred Fish, Don Marti, Jim Meyering, Peter Norvig и Julian Seward достали разрешения на воспроизведение различных элементов, процитированных по всей книге. Спасибо Geoff Collyer, Ulrich Drepper, Yosef Gold, д-ру C.A.R. (Tony) Hoare, д-ру Manny Lehman, Jim Meyering, д-ру Dennis M. Ritchie, Julian Seward, Henry Spencer и д-ру Wladyslaw M. Turski за предоставление множества полезной общей информации. Спасибо также другим членам группы GNITS. Karl Berry, Akim DeMaille, Ulrich Drepper, Greg McGary, Jim Meyering, Francois Pinard и Tom Tromey, которые предоставили полезную обратную связь относительно хорошей практики программирования. Karl Berry, Alper Ersoy и д-р Nelson H.F. Beebe предоставили ценную техническую помощь по Texinfo и DocBook/XML.</p> <p class="book">Хорошие технические обзоры не только гарантируют, что автор использует правильные факты, они также гарантируют, что он тщательно обдумывает свое представление. Д-р Nelson H.F. Beebe, Geoff Collyer, Russ Cox, Ulrich Drepper, Randy Lechlitner, д-р Brian W. Kernighan, Peter Memishian, Jim Meyering, Chet Ramey и Louis Taber работали в качестве технических рецензентов для всей книги. Д-р Michael Brennan предоставил полезные комментарии для главы 15. Их рецензии принесли пользу как содержанию, так и многим примерам программ. Настоящим я благодарю их всех. Как обычно говорят в таких случаях большинство авторов, «все оставшиеся ошибки мои».</p> <p class="book">Я особенно хотел бы поблагодарить Mark Taub из Pearson Education за инициирование этого проекта, за его энтузиазм для этой серии и за его помощь и советы по мере прохождения книги через различные ее стадии. Anthony Gemmellaro сделал феноменальную работу по реализации моей идеи для обложки, а внутренний дизайн Gail Cocker великолепен. Faye Gemmellaro сделал процесс производства вместо рутины приятным. Dmitry Kirsanov и Alina Kirsanova сделали рисунки, макеты страниц и предметный указатель; работать с ними было одно удовольствие.</p> <p class="book">Наконец, моя глубочайшая благодарность жене Мириам за ее поддержку и ободрение во время написания книги.</p> <blockquote class="book"><blockquote class="book"><i>Арнольд Роббинс</i></blockquote><blockquote class="book"><i>Nof Ayalon</i></blockquote><blockquote class="book"><i>ИЗРАИЛЬ</i></blockquote> </blockquote> <a name="t2"></a><h3 class="book">Часть 1<br>Файлы и пользователи<br> </h3> <a name="t3"></a><h3 class="book"> Глава 1<br> Введение<br></h3> <p class="book">Если есть одна фраза, резюмирующая важнейшие понятия GNU/Linux (а следовательно, и Unix), это «файлы и процессы». В данной главе мы рассмотрим модели файлов и процессов в Linux. Их важно понять, потому что почти все системные вызовы имеют отношение к изменению какого-либо атрибута или части состояния файла или процесса.</p> <p class="book">Далее, поскольку мы будем изучать код в обеих стилях, мы кратко рассмотрим главные различия между стандартным С 1990 г. и первоначальным С. Наконец, мы довольно подробно обсудим то, что делает GNU-программы «лучше» — принципы программирования, использование которых в коде мы увидим.</p> <p class="book">В данной главе содержится ряд умышленных упрощений. Детали в подробностях будут освещены по мере продвижения по книге. Если вы уже являетесь специалистом в Linux, пожалуйста, простите нас</p> <a name="t4"></a><h3 class="book">1.1. Модель файловой системы Linux/Unix<br> </h3> <p class="book">Одной из движущих целей первоначального проекта Unix была простота. Простые понятия легко изучать и использовать. Когда понятия переведены в простые API, легко проектировать, писать и отлаживать простые программы. Вдобавок, простой код часто занимает меньше места и он более эффективен, чем более усложненные проекты.</p><p class="book">Поиск простоты направлялся двумя факторами. С технической точки зрения, первоначальные мини-компьютеры PDP-11, на которых разрабатывалась Unix, имели маленькое адресное пространство: 64 килобайта на меньших системах, 64 Кб кода и 64 Кб данных на больших. Эти ограничения относились не только к обычным программам (так называемому коду <i>уровня пользователя</i>), но и к самой операционной системе (коду <i>уровня ядра</i>). Поэтому не только «Маленький — значит красивый» в эстетическом смысле, но «Маленький — значит красивый», потому что не было другого выбора!</p><p class="book">Вторым фактором была отрицательная реакция на современные коммерческие операционные системы, которые были без надобности усложнены, со сложными командными языками, множеством разновидностей файлового ввода-вывода и слабой общностью или гармонией. (Стив Джонсон однажды заметил: «Использование TSO подобно пинанию мертвого кита на побережье». TSO — это как раз одна из только что описанных бестолковых систем с разделением времени «для мэйнфреймов.)</p><a name="t5"></a><h3 class="book"> 1.1.1. Файлы и права доступа<br></h3><p class="book">Файловая модель Unix проста, как фраза: файл — это линейный поток байтов. Точка. Операционная система не накладывает на файлы никаких предопределенных структур, ни фиксированных или переменных размеров записей, ни индексированных файлов, ничего. Интерпретация содержимого файла целиком оставлена приложению. (Это не совсем верно, как мы вскоре увидим, но для начала достаточно близко к истине.)</p><p class="book">Если у вас есть файл, вы можете сделать с данными в файле три вещи: прочитать, записать или исполнить их.</p><p class="book">Unix разрабатывался для мини-компьютеров с разделением времени; это предполагает наличие с самого начала многопользовательского окружения. Раз есть множество пользователей, должно быть возможным указание прав доступа к файлам: возможно, пользователь <code>jane</code> является начальником пользователя <code>fred</code>, и <code>jane</code> не хочет, чтобы <code>fred</code> прочел последние результаты аттестации.</p><p class="book">В целях создания прав доступа пользователи подразделяются на три различные категории: <i>владелец</i> файла; <i>группа</i> пользователей, связанная с данным файлом (вскоре будет пояснено); и <i>остальные пользователи</i>. Для каждой из этих категорий <i>каждый</i> файл имеет отдельные, связанные с этим файлом, биты прав доступа, разрешающие чтение, запись и исполнение. Эти разрешения отображаются в первом поле вывода команды '<code>ls -l</code>':</p><p class="book"><code>$ <b>ls -l progex.texi</b></code></p><p class="book"><code>-rw-r--r-- 1 arnold devel 5614 Feb 24 18:02 progex.texi</code></p><p class="book">Здесь arnold и devel являются соответственно владельцем и группой файла <code>progex.texi</code>, a <code>-rw-r--r--</code> является строкой типа файла и прав доступа. Для обычного файла первым символом будет дефис, для каталогов - <code>d</code>, а для других видов файлов - небольшой набор других символов, которые пока не имеют значения. Каждая последующая тройка символов представляют права на чтение, запись и исполнение для владельца, группы и «остальных» соответственно.</p><p class="book">В данном примере файл <code>progex.texi</code> может читать и записывать владелец файла, а группа и остальные пользователи могут только читать. Дефисы означают отсутствие разрешений, поэтому этот файл никто не может исполнить, а группа и остальные пользователи не могут в него записывать.</p><p class="book">Владелец и группа файла хранятся в виде числовых значений, известных как идентификатор пользователя (user ID — UID) и идентификатор группы (group ID — GID); стандартные библиотечные функции, которые мы рассмотрим далее в книге, позволяют напечатать эти значения в виде читаемых имен.</p><p class="book">Владелец файла может изменить разрешения, используя команду <code>chmod</code> (change mode — изменить режим). (Права доступа к файлу, по существу, иногда называют «режимом файла».) Группу файла можно изменить с помощью команд <code>chgrp</code> (change group — изменить группу) и <code>chown</code> (change owner — сменить владельца)<sup><a name="anotelink11" id="anotelink11" href="#n11" title=" Некоторые системы позволяют рядовым пользователям назначать владельцем их файла кого-нибудь еще, таким образом «отдавая его». Детали определяются стандартом POSIX, но они несколько запутаны. Обычная конфигурация GNU/Linux не допускает этого — Примеч. автора.">[11]</a></sup>.</p><p class="book">Групповые права доступа были нацелены на поддержку совместной работы: хотя определенным файлом может владеть один член группы или подразделения, возможно, каждый член группы должен иметь возможность изменять его. (Рассмотрите совместный маркетинговый доклад или данные исследования.)</p><p class="book">Когда система проверяет доступ к файлу (обычно при открытии файла), если UID процесса совпадает с UID файла, используются права доступа владельца файла. Если эти права доступа запрещают операцию (скажем, попытка записи в файл с доступом <code>-r--rw-rw-</code>), операция завершается неудачей; Unix и Linux не продолжают проверку прав доступа для группы и других пользователей<sup><a name="anotelink12" id="anotelink12" href="#n12" title=" Конечно, владелец всегда может изменить права доступа. Большинство пользователей не отменяют для себя нрава на запись — Примеч. автора.">[12]</a></sup>. Это верно также, если UID различаются, но совпадают GID; если права доступа группы запрещают операцию, она завершается неудачей.</p><p class="book">Unix и Linux поддерживают понятие <i>суперпользователя (superuser)</i>: это пользователь с особыми привилегиями. Этот пользователь известен как root и имеет UID, равный 0. root позволено делать все; никаких проверок, все двери открыты, все ящики отперты.<sup><a name="anotelink13" id="anotelink13" href="#n13" title=" Для этого правила есть несколько редких исключений, но все они выходят за рамки данной книги — Примеч. автора.">[13]</a></sup> (Это может иметь важные последствия для безопасности, которых мы будем касаться по всей книге, но не будем освещать исчерпывающе.) Поэтому, даже если файл имеет режим <code>----------</code>, <code>root</code> все равно может читать файл и записывать в него. (Исключением является то, что файл нельзя исполнить. Но поскольку <code>root</code> может добавить право на исполнение, это ограничение ничего не предотвращает.)</p><p class="book">Модель прав доступа владелец/группа/другие, чтение/запись/исполнение проста, тем не менее достаточно гибка, чтобы охватывать большинство ситуаций. Существуют другие, более мощные, но и более сложные модели, реализованные на других системах, но ни одна из них не стандартизирована достаточно хорошо и не реализована достаточно широко, чтобы заслуживать обсуждения в общем руководстве, подобном этому.</p><a name="t6"></a><h3 class="book"> 1.1.2. Каталоги и имена файлов<br></h3><p class="book">Раз у вас есть файл, нужно где-то его хранить. В этом назначение <i>каталога</i> (известного в системах Windows или Apple Macintosh под названием «папка»). Каталог является особой разновидностью файла, связывающего имена файлов с метаданными, известными как <i>узлы</i> (inodes). Каталоги являются особыми, поскольку их может обновлять лишь операционная система путем описанных в главе 4, «Файлы и файловый ввод-вывод», системных вызовов. Они особые также потому, что операционная система предписывает формат элементов каталога.</p><p class="book">Имена файлов могут содержать любой 8-битный байт, за исключением символа '<code>/</code>' (прямой косой черты) и ASCII символа NUL, все биты которого содержат 0. Ранние Unix- системы ограничивали имена 14 байтами; современные системы допускают отдельные имена файлов вплоть до 255 байтов.</p><p class="book">Узел содержит всю информацию о файле, за исключением его имени: тип, владелец, группа, права допуска, размер, времена изменения и доступа. Он хранит также размещение на диске блоков, содержащих данные файла. Все это данные <i>о</i> файле, а не данные самого файла, отсюда термин <i>метаданные</i>.</p><p class="book">Права доступа к каталогам по сравнению с правами доступа к файлам имеют несколько другой смысл. Разрешение на чтение означает возможность поиска в каталоге, т.е. его просмотр с целью определить, какие файлы в нем содержатся. Разрешение на запись дает возможность создавать и <i>удалять</i> файлы в каталоге. Разрешение на исполнение означает возможность прохода через каталог при открытии или ином доступе к содержащемуся файлу или подкаталогу.</p><blockquote class="book"> <p class="book"><b>ЗАМЕЧАНИЕ</b>. Если у вас есть разрешение на запись в каталог, вы можете удалять файлы из этого каталога, даже если они не принадлежат вам! При интерактивной работе команда rm отмечает это, запрашивая в таком случае подтверждение</p> <p class="book">Каталог <code>/tmp</code> имеет разрешение на запись для каждого, но ваши файлы в <code>/tmp</code> находятся вполне в безопасности, поскольку <code>/tmp</code> обычно имеет установленный так называемый «липкий» (sticky) бит:</p> <p class="book"><code>$ <b>ls -ld /trap</b></code></p> <p class="book"><code>drwxrwxrwt 11 root root 4096 May 15 17:11 /tmp</code></p> <p class="book">Обратите внимание, что t находится в последней позиции первого поля. В большинстве каталогов в этом месте стоит x. При установленном «липком» бите ваши файлы можете удалять лишь вы, как владелец файла, или <code>root</code>. (Более детально это обсуждается в разделе 11.2 5, «Каталоги и липкий бит».)</p></blockquote><a name="t7"></a><h3 class="book"> 1.1.3. Исполняемые файлы<br></h3><p class="book">Помните, мы говорили, что операционная система на накладывает структуру на файлы? Мы уже видели, что это было невинной ложью относительно каталогов. Это же относится к двоичным исполняемым файлам. Чтобы запустить программу, ядро должно знать, какая часть файла представляет инструкции (код), а какая — данные. Это ведет к понятию формата <i>объектного файла</i>, которое определяет, как эти данные располагаются внутри файла на диске.</p><p class="book">Хотя ядро запустит лишь файлы, имеющие соответствующий формат, создание таких файлов задача утилит режима пользователя. Компилятор с языка программирования (такого как Ada, Fortran, С или С++) создает объектные файлы, а затем компоновщик или загрузчик (обычно с именем <code>ld</code>) связывает объектные файлы с библиотечными функциями для окончательного создания исполняемого файла. Обратите внимание, что даже если все нужные биты в файле размешены в нужных местах, ядро не запустит его, если не установлен соответствующий бит, разрешающий исполнение (или хотя бы один исполняющий бит для <code>root</code>).</p><p class="book">Поскольку компилятор, ассемблер и загрузчик являются инструментами режима пользователя, изменить со временем по мере необходимости форматы объектных файлов (сравнительно) просто; надо только «научить» ядро новому формату, и он может быть использован. Часть ядра, загружающая исполняемые файлы, относительно невелика, и это не является невозможной задачей. Поэтому форматы файлов Unix развиваются с течением времени. Первоначальный формат был известен как a.out (Assembler OUTput — вывод сборщика). Следующий формат, до сих пор использующийся в некоторых коммерческих системах, известен как COFF (Common Object File Format&nbsp;— общий формат объектных файлов), а современный, наиболее широко использующийся формат — ELF (Extensible Linking Format — открытый формат компоновки). Современные системы GNU/Linux используют ELF.</p><p class="book">Ядро распознает, что исполняемый файл содержит двоичный объектный код, проверяя первые несколько байтов файла на предмет совпадения со специальными <i>магическими числами</i>. Это последовательности двух или четырех байтов, которые ядро распознает в качестве специальных. Для обратной совместимости современные Unix-системы распознают несколько форматов. Файлы ELF начинаются с четырех символов «<code>\177ELF</code>».</p><p class="book">Помимо двоичных исполняемых файлов, ядро поддерживает также исполняемые <i>сценарии (скрипты)</i>. Такой файл также начинается с магического числа: в этом случае, это два обычных символа <code># !</code> . Сценарий является программой, исполняемой интерпретатором, таким, как командный процессор, awk, Perl, Python или Tcl. Строка, начинающаяся с <code>#!</code>, предоставляет полный путь к интерпретатору и один необязательный аргумент:</p><p class="book"><code>#! /bin/awk -f</code></p><p class="book"><code>BEGIN {print "hello, world"}</code></p><p class="book">Предположим, указанное содержимое располагается в файле <code>hello.awk</code> и этот файл исполняемый. Когда вы набираете '<code>hello.awk</code>', ядро запускает программу, как если бы вы напечатали '<code>/bin/awk -f hello.awk</code>'. Любые дополнительные аргументы командной строки также передаются программе. В этом случае, <code>awk</code> запускает программу и отображает общеизвестное сообщение <code>hello, world</code>.</p><p class="book">Механизм с использованием <code>#!</code> является элегантным способом скрыть различие между двоичными исполняемыми файлами и сценариями. Если <code>hello.awk</code> переименовать просто в <code>hello</code>, пользователь, набирающий '<code>hello</code>', не сможет сказать (и, конечно, не должен знать), что <code>hello</code> не является двоичной исполняемой программой.</p><a name="t8"></a><h3 class="book"> 1.1.4. Устройства<br></h3><p class="book">Одним из самых замечательных новшеств Unix было объединение файлового ввода- вывода и ввода-вывода от устройств.<sup><a name="anotelink14" id="anotelink14" href="#n14" title=" Эта особенность впервые появилась в Multics, но Multics никогда широко не использовался — Примеч. автора.">[14]</a></sup> Устройства выглядят в файловой системе как файлы, для доступа к ним используются обычные права доступа, а для их открытия, чтения, записи и закрытия используются те же самые системные вызовы ввода-вывода. Вся «магия», заставляющая устройства выглядеть подобно файлам, скрыта в ядре. Это просто другой аспект движущего принципа простоты в действии, мы можем выразить это как <i>никаких частных случаев для кода пользователя</i>.</p><p class="book">В повседневной практике, в частности, на уровне оболочки, часто появляются два устройства: <code>/dev/null</code> и <code>/dev/tty</code>.</p><p class="book"><code>/dev/null</code> является «битоприемником». Все данные, посылаемые <code>/dev/null</code>, уничтожаются операционной системой, а все попытки прочесть отсюда немедленно возвращают конец файла (EOF).</p><p class="book"><code>/dev/tty</code> является текущим управляющим терминалом процесса — тем, который он слушает, когда пользователь набирает символ прерывания (обычно CTRL-C) или выполняет управление заданием (CTRL-Z).</p><p class="book">Системы GNU/Linux и многие современные системы Unix предоставляют устройства <code>/dev/stdin</code>, <code>/dev/stdout</code> и <code>/dev/stderr</code>, которые дают возможность указать открытые файлы, которые каждый процесс наследует при своем запуске.</p><p class="book">Другие устройства представляют реальное оборудование, такое, как ленточные и дисковые приводы, приводы CD-ROM и последовательные порты. Имеются также программные устройства, такие, как псевдотерминалы, которые используются для сетевых входов в систему и систем управления окнами, <code>/dev/console</code> представляет системную консоль, особое аппаратное устройство мини-компьютеров. В современных компьютерах <code>/dev/console</code> представлен экраном и клавиатурой, но это может быть также и последовательный порт</p><p class="book">К сожалению, соглашения по именованию устройств не стандартизированы, и каждая операционная система использует для лент, дисков и т.п. собственные имена. (К счастью, это не представляет проблемы для того, что мы рассматриваем в данной книге.) Устройства имеют в выводе '<code>ls -l</code>' в качестве первого символа <code>b</code> или <code>с</code>.</p><p class="book"><code>$ <b>ls -l /dev/tty /dev/hda</b></code></p><p class="book"><code>brw-rw-rw- 1 root disk 3, 0 Aug 31 02:31 /dev/hda</code></p><p class="book"><code>crw-rw-rw- 1 root root 5, 0 Feb 26 08:44 /dev/tty</code></p><p class="book">Начальная '<code>b</code>' представляет блочные устройства, а '<code>c</code>' представляет символьные устройства. Файлы устройств обсуждаются далее в разделе 5.4, «Получение информации о файлах».</p><a name="t9"></a><h3 class="book">1.2. Модель процессов Linux/Unix<br> </h3> <p class="book">Процесс является работающей программой.<sup><a name="anotelink15" id="anotelink15" href="#n15" title=" Процесс может быть приостановлен, в этом случае он не «работающий»; но он и не завершён. В любом случае, на ранних этапах восхождения по кривой обучения не стоит быть слишком педантичным — Примеч. автора.">[15]</a></sup> Процесс имеет следующие атрибуты:</p><p class="book">уникальный идентификатор процесса (PID);</p><p class="book">•&nbsp;родительский процесс (с соответствующим идентификатором, PPID);</p><p class="book">•&nbsp;идентификаторы прав доступа (UID, GID, набор групп и т.д.);</p><p class="book">•&nbsp;отдельное от всех других процессов адресное пространство;</p><p class="book">•&nbsp;программа, работающая в этом адресном пространстве;</p><p class="book">•&nbsp;текущий рабочий каталог ('<code>.</code>');</p><p class="book">•&nbsp;текущий корневой каталог (<code>/</code>; его изменение является продвинутой темой);</p><p class="book">•&nbsp;набор открытых файлов, каталогов, или и того, и другого;</p><p class="book">•&nbsp;маска запретов доступа, использующаяся при создании новых файлов;</p><p class="book">•&nbsp;набор строк, представляющих окружение<sup><a name="anotelink16" id="anotelink16" href="#n16" title=" Так называемые переменные окружения — Примеч. науч. ред.">[16]</a></sup>;</p><p class="book">•&nbsp;приоритеты распределения времени процессора (продвинутая тема);</p><p class="book">•&nbsp;установки для размещения сигналов (signal disposition) (продвинутая тема); управляющий терминал (тоже продвинутая тема).</p><p class="book">Когда функция <code>main()</code> начинает исполнение, все эти вещи уже помещены в работающей программе на свои места. Для запроса и изменения каждого из этих вышеназванных элементов доступны системные вызовы; их освещение является целью данной книги.</p><p class="book">Новые процессы всегда создаются существующими процессами. Существующий процесс называется <i>родительским</i>, а новый процесс — <i>порожденным</i>. При загрузке ядро вручную создает первый, изначальный процесс, который запускает программу <code>/sbin/init</code>; идентификатор этого процесса равен 1, он осуществляет несколько административных функций. Все остальные процессы являются потомками <code>init</code>. (Родительским процессом <code>init</code> является ядро, часто обозначаемое в списках как процесс с ID 0.)</p><p class="book">Отношение порожденный-родительский является отношением один к одному; у каждого процесса есть только один родитель, поэтому легко выяснить PID родителя. Отношение родительский-порожденный является отношением один ко многим; каждый данный процесс может создать потенциально неограниченное число порожденных. Поэтому для процесса нет простого способа выяснить все PID своих потомков. (Во всяком случае, на практике это не требуется.) Родительский процесс можно настроить так, чтобы он получал уведомление при завершении порожденного процесса, он может также явным образом ожидать наступления такого события.</p><p class="book">Адресное пространство (память) каждого процесса отделена от адресного пространства всех остальных процессов. Если два процесса не договорились явным образом разделять память, один процесс не может повлиять на адресное пространство другого. Это важно; это обеспечивает базовый уровень безопасности и надежности системы. (В целях эффективности, система разделяет исполняемый код одной программы с правом доступа только для чтения между всеми процессами, запустившими эту программу. Это прозрачно для пользователя и запущенной программы.)</p><p class="book">Текущий рабочий каталог — это каталог, относительно которого отсчитываются относительные пути файлов (те, которые не начинаются с '<code>/</code>'). Это каталог, в котором вы находитесь, когда набираете команду оболочки '<code>cd <i>someplace</i></code>'.</p><p class="book">По соглашению, все программы запускаются с тремя уже открытыми файлами: стандартным вводом, стандартным выводом и стандартной ошибкой. Это места, откуда принимается ввод, куда направляется вывод и куда направляются сообщения об ошибках соответственно. На протяжении этой книги мы увидим, как они назначаются. Родительский процесс может открыть дополнительные файлы и сделать их доступными для порожденных процессов; порожденный процесс должен каким-то образом узнать, что они есть, либо посредством какого-либо соглашения, либо через аргументы командной строки или переменную окружения.</p><p class="book"><i>Окружение</i> представляет собой набор строк, каждая в виде '<code>имя=значение</code>'. Для запроса и установки значений переменных окружения имеются специальные функции, а порожденные процессы наследуют окружение своих родителей. Типичными переменными окружения оболочки являются PATH и НОМЕ. Многие программы для управления своим поведением полагаются на наличие и значения определенных переменных окружения.</p><p class="book">Важно понять, что один процесс в течение своего существования может исполнить множество программ. <i>Все</i> устанавливаемые системой атрибуты (текущий каталог, открытые файлы, PID и т.д.) остаются теми же самыми, если только они не изменены явным образом. Отделение «запуска нового процесса» от «выбора программы для запуска» является ключевым нововведением Unix. Это упрощает многие операции. Другие операционные системы, которые объединяют эти две операции, являются менее общими и их сложнее использовать.</p><a name="t10"></a><h3 class="book"> 1.2.1. Каналы: сцепление процессов<br></h3><p class="book">Без сомнения, вам приходилось использовать конструкцию ('<code>|</code>') оболочки для соединения двух или более запущенных программ. Канал действует подобно файлу: один процесс записывает в него, используя обычную операцию записи, а другой процесс считывает из него с помощью операции чтения. Процессы (обычно) не знают, что их ввод/вывод является каналом, а не обычным файлом.</p><p class="book">Как ядро скрывает «магию» для устройств, заставляя их действовать подобно файлам, точно так же оно проделывает эту работу для каналов, принимая меры по задержке записи в канал при его наполнении и задержке чтения, когда нет ожидающих чтения данных.</p><p class="book">Таким образом, принцип файлового ввода/вывода применительно к каналам служит ключевым механизмом для связывания запушенных программ; не требуется никаких временных файлов. Опять-таки общность и простота работы: никаких особых случаев для кода пользователя.</p><a name="t11"></a><h3 class="book">1.3. Стандартный С против оригинального С<br> </h3> <p class="book">В течение многих лет определение С де-факто можно было найти в первом издании книги Брайана Кернигана и Денниса Ричи «Язык программирования С» (Brian Kernighan &amp; Dennis Ritchie, <i>The С Programming Language</i>). Эта книга описала С, как он существовал для Unix и на системах, на которые его перенесли разработчики лаборатории Bell Labs. На протяжении данной книги мы называем его как «оригинальный С», хотя обычным является также название «С Кернигана и Ричи» («K&amp;R С»), по именам двух авторов книги. (Деннис Ричи разработал и реализовал С.)</p> <p class="book">Стандарт ISO С 1990 г.<sup><a name="anotelink17" id="anotelink17" href="#n17" title=" Между народный стандарт ISO/IEC 9899-1990 описывает разновидность языка С известную также как C89 или C90 — Примеч. науч. ред.">[17]</a></sup> формализовал определения языка, включая функции библиотеки С (такие, как <code>printf()</code> и <code>fopen()</code>). Комитет по стандартам С проделал замечательную работу по стандартизации существующей практики и избежал введения новых возможностей, с одним значительным исключением (и несколькими незначительными). Наиболее заметным изменением языка было использование прототипов функций, заимствованных от С++.</p> <p class="book">Стандартные языки программирования С, C++ и Java используют прототипы функций для объявлений и определений функций. Прототип описывает не только возвращаемое значение функции, но также и число и тип ее аргументов. С прототипами компилятор может выполнить проверку типов в точке вызова функции:</p> <p class="book"><i>Объявление</i></p> <p class="book"><code>extern int myfunc(struct my_struct *a,</code></p> <p class="book"><code>&nbsp;struct my_struct *b, double c, int d);</code></p> <p class="book"><i>Определение</i></p> <p class="book"><code>int myfunc(struct my_struct *a,</code></p> <p class="book"><code>&nbsp;struct my_struct *b, double c, int d) {</code></p> <p class="book"><code>&nbsp;...</code></p> <p class="book"><code>}</code></p> <p class="book"><code>...</code></p> <p class="book"><code>struct my_struct s, t;</code></p> <p class="book"><code>int j;</code></p> <p class="book"><code>...</code></p> <p class="book"><code>/* Вызов функции, где-то в другом месте: */</code></p> <p class="book"><code>j = my_func(&amp;s, &amp;t, 3.1415, 42);</code></p> <p class="book">Это правильный вызов функции. Но рассмотрите ошибочный вызов:</p> <p class="book"><code>j = my_func(-1, -2, 0);</code></p> <p class="book"><code>/* Ошибочные число и типы аргументов */</code></p> <p class="book">Компилятор может сразу же определить этот вызов как неверный. Однако, в оригинальном С функции объявляются без указания списка аргументов:</p> <p class="book"><code>extern int myfunc();</code></p> <p class="book"><code>/* Возвращает int, аргументы неизвестны */</code></p> <p class="book">Более того, определения функций перечисляют имена параметров в заголовке функции, затем объявляют параметры перед телом функции. Параметры типа int объявлять не нужно, и если функция возвращает int, его тоже не нужно объявлять:</p> <p class="book"><code>myfunc(a, b, с, d); /* Возвращаемый тип int*/</code></p> <p class="book"><code>struct my_struct *а, *b;</code></p> <p class="book"><code>double с;</code></p> <p class="book"><code>/* Обратите внимание, нет объявления параметра d*/</code></p> <p class="book"><code>{</code></p> <p class="book"><code>&nbsp;...</code></p> <p class="book"><code>}</code></p> <p class="book">Рассмотрите снова тот же ошибочный вызов функции: '<code>j = my_func(-1, -2 , 0);</code>'. В оригинальном С у компилятора нет возможности узнать, что вы (ошибочно, полагаем) передали <code>my_func()</code> ошибочные аргументы. Подобные ошибочные вызовы обычно приводят к трудно устранимым проблемам времени исполнения (таким, как ошибки сегментации, из-за чего программа завершается), и для работы с такими вещами была создана программа Unix <code>lint</code>.</p> <p class="book">Поэтому, хотя прототипы функции и были радикальным отходом от существующей практики, дополнительную проверку типов посчитали слишком важной, чтобы обходиться без нее, и после небольшого сопротивления она была добавлена в язык.</p> <p class="book">Для С стандарта 1990 г. код, написанный в оригинальном стиле, является действительным как для объявлений, так и для определений. Это дает возможность продолжать компилировать миллионы строк существующего кода с помощью компилятора, удовлетворяющего стандарту. Новый код, очевидно, должен быть написан с прототипами из-за улучшенных возможностей проверки ошибок времени компилирования.</p> <p class="book">Стандарт С 1999 г.<sup><a name="anotelink18" id="anotelink18" href="#n18" title=" Стандарт C99 (ISO/IЕС 9899-1999) — Примеч. науч. ред.">[18]</a></sup> продолжает допускать объявления и определения в оригинальном стиле. Однако, правило «неявного <code>int</code>» было убрано; функции должны иметь возвращаемый тип, а все параметры должны быть объявлены.</p> <p class="book">Более того, когда программа вызывала функцию, которая не была формально объявлена, оригинальный С создал бы для функции неявное объявление с возвращаемым типом <code>int</code>. С стандарта 1999 г. делал то же самое, дополнительно отметив, что у него не было информации о параметрах. С стандарта 1999 г. не предоставляет больше возможности «автоматического объявления».</p> <p class="book">Другими заметными дополнениями в стандарте С являются ключевое слово <code>const</code>, также из С++, и ключевое слово <code>volatile</code>, которое придумал комитет. Для кода, который вы увидите в этой книге, наиболее важной вещью является понимание различных синтаксисов объявлений и определений функций.</p> <p class="book">Для кода V7, использующего определения в оригинальном стиле, мы добавили комментарии, показывающие эквивалентный прототип. В остальных случаях мы оставили код как есть, предпочитая показать его точно таким, каким он был первоначально написан, и как бы вы его увидели, если бы сами загрузили код.</p> <p class="book">Хотя стандарт С 1999 г. добавляет некоторые дополнительные ключевые слова и возможности, отсутствующие в версии 1990 г., мы решили придерживаться диалекта 1990 г, поскольку компиляторы C99 не являются пока типичными. Практически, это не имеет значения: код C89 должен компилироваться и запускаться без изменений при использовании компилятора C99, а новые возможности C99 не затрагивают наше обсуждение или использование фундаментальных API Linux/Unix.</p> <a name="t12"></a><h3 class="book">1.4. Почему программы GNU лучше<br> </h3> <p class="book">Что делает программу GNU программой GNU?<sup><a name="anotelink19" id="anotelink19" href="#n19" title=" Этот раздел адаптирован из статьи автора, который издавался в выпуске 16 Linux Journal (См. http://www.linuxjournal.com/article.php?sid=1135) Перепечатано и адаптировано по разрешению — Примеч. автора.">[19]</a></sup> Что делает программное обеспечение GNU «лучше» по сравнению с другим (платным или бесплатным) программным обеспечением? Наиболее очевидной разницей является общедоступная лицензия (General Public License — GPL), которая описывает условия распространения для программного обеспечения GNU. Но это обычно не причина, чтобы вы могли услышать, как люди говорят: «Дайте GNU-версию <code>xyz</code>, она намного лучше». Программное обеспечение GNU в общем более устойчиво, имеет лучшую производительность, чем в стандартных версиях Unix. В данном разделе мы рассмотрим некоторые причины этого явления, а также рассмотрим документ, описывающий принципы проектирования программного обеспечения GNU.</p><p class="book">«Стандарты кодирования GNU» (<i>GNU Coding Standards</i>) описывают создание программного обеспечения для проекта GNU. Они охватывает ряд тем. Вы можете найти <i>GNU Coding Standards</i> по адресу <code>http://www.gnu.org/prep/standards.html</code>. Смотрите в онлайн-версии ссылки на исходные файлы в других форматах.</p><p class="book">В данном разделе мы описываем лишь те части <i>GNU Coding Standards</i>, которые относятся к проектированию и реализации программ.</p><a name="t13"></a><h3 class="book"> 1.4.1. Проектирование программ<br></h3><p class="book">Глава 3 <i>GNU Coding Standards</i> содержит общие советы относительно проектирования программ. Четырьмя главными проблемами являются совместимость (со стандартами и с Unix), язык, использование нестандартных возможностей других программ (одним словом, «ничего»), и смысл «переносимости».</p><p class="book">Важной целью является совместимость со стандартом С и POSIX, а также, в меньшей степени, с Berkley Unix. Но она не преобладает. Общей идеей является предоставление всех необходимых возможностей через аргументы командной строки для предоставления точного режима ISO или POSIX.</p><p class="book">Предпочтительным языком для написания программного обеспечения GNU является С, поскольку это наиболее доступный язык. В мире Unix стандарт С теперь обычен, но если для вас не представляет труда поддержка оригинального С, вы должны сделать это. Хотя стандарты кодирования отдают предпочтение С перед С++, C++ теперь тоже вполне обычен. Примером широко используемого пакета GNU, написанного на С++, является <code>groff</code> (GNU <code>troff</code>). Наш опыт говорит, что с GCC, поддерживающим С++, установка <code>groff</code> не представляет сложности.</p><p class="book">Стандарты утверждают, что переносимость является чем-то вроде отвлекающего маневра. Утилиты GNU ясно нацелены на работу с ядром GNU и с библиотекой GNU С<sup><a name="anotelink20" id="anotelink20" href="#n20" title=" Это утверждение относится к ядру HURD, которое все еще находится в стадии разработки (в начале 2004 г.) Разработка на основе GCC и библиотеки GNU С (GLIBC) сегодня имеет место большей частью на Linux-системах — Примеч. автора.">[20]</a></sup>. Но поскольку ядро еще не завершено, и пользователи используют инструменты GNU на не-GNU системах, переносимость желательна, но не является первостепенной задачей. Стандарт рекомендует для достижения переносимости между различными системами Unix использовать Autoconf.</p><a name="t14"></a><h3 class="book"> 1.4.2. Поведение программы<br></h3><p class="book">Глава 4 <i>GNU Coding Standards</i> предоставляет общие советы относительно поведения программы. Ниже мы вернемся к одному из ее разделов для более подробного рассмотрения. Глава фокусируется на строении программы, форматировании сообщений об ошибках, написании библиотек (делая их рентабельными) и стандартах для интерфейса командной строки.</p><p class="book">Форматирование сообщений об ошибках важно, поскольку несколько инструментов, особенно Emacs, используют сообщения об ошибках, чтобы помочь вам попасть в то место в исходном файле или файле данных, где произошла ошибка.</p><p class="book">Утилиты GNU должны использовать для обработки командной строки функцию <code>getopt_long()</code>. Эта функция предусматривает разбор аргументов командной строки как для опций в стиле традиционного Unix ('<code>gawk -F:...</code>'), так и для длинных опций в стиле GNU ('<code>gawk --field-separator=:...</code>'). Все программы должны предусматривать опции <code>--help</code> и <code>--version</code>, а когда в одной программе используется длинное имя, оно таким же образом должно использоваться и в другой программе GNU. Для этой цели есть довольно полный список длинных опций, используемых современными GNU-программами.</p><p class="book">В качестве простого, но очевидного примера, <code>--verbose</code> пишется точно таким же способом во всех GNU-программах. Сравните это с <code>-v</code>, <code>-V</code>, <code>-d</code> и т.д. во многих других программах Unix. Большая часть главы 2, «Аргументы, опции и окружение», с. 23, посвящена механике разбора аргументов и опций.</p><a name="t15"></a><h3 class="book"> 1.4.3. Программирование на С<br></h3><p class="book">Наиболее привлекательной частью <i>GNU Coding Standards</i> является глава 5, которая описывает написание кода на С, освещая такие темы, как форматирование кода, правильное использование комментариев, чистое использование С, именование ваших функций и переменных, а также объявление или не объявление стандартных системных функций, которые вы хотите использовать.</p><p class="book">Форматирование кода является религиозной проблемой; у многих людей разные стили, которые они предпочитают. Лично нам не нравится стиль FSF, и если вы взглянете на <code>gawk</code>, который мы поддерживаем, вы увидите, что он форматирован в стандартном стиле K&amp;R (стиль расположения кода, использованный в обоих изданиях книги Кернигана и Ричи). Но это единственное отклонение в <code>gawk</code> от этой части стандартов кодирования.</p><p class="book">Тем не менее, хотя нам и не нравится стиль FSF<sup><a name="anotelink21" id="anotelink21" href="#n21" title=" Стиль расположения кода, рекомендуемый фондом свободного программного обеспечения (Free Software Foundation)&nbsp;— Примеч. науч. ред.">[21]</a></sup>, мы чувствуем, что при модификации некоторых других программ, придерживание уже использованного стиля кода является исключительно важным. Последовательность в стиле кода более важна, чем сам стиль, который вы выбираете. <i>GNU Coding Standards</i> дает такой же совет. (Иногда невозможно обнаружить последовательный стиль кода, в этом случае программа, возможно, испорчена использованием <code>indent</code> от GNU или <code>cb</code> от Unix.)</p><p class="book">Что мы сочли важным в главе о написании кода на С, это то, что эти советы хороши для <i>любого</i> кода на С, а не только когда вы работаете над программой GNU. Поэтому, если вы просто учите С или даже если вы уже работали некоторое время на С (или С++), мы рекомендуем вам эту главу, поскольку она заключает в себе многолетний опыт.</p><a name="t16"></a><h3 class="book"> 1.4.4. Вещи, которые делают программы GNU лучше<br></h3><p class="book">Теперь мы рассмотрим раздел, озаглавленный «<i>Написание надежных программ</i>», в главе 4 «<i>Поведение программ для всех программ</i>». Этот раздел описывает принципы проектирования программного обеспечения, которые делают программы GNU лучше их двойников в Unix Мы процитируем выбранные части главы, с несколькими примерами случаев, в которых эти принципы окупились.</p><blockquote class="book"> <p class="book">Избегайте произвольных ограничений длины или числа любой структуры данных, включая имена файлов, строки, файлы и символы, выделяя все структуры данных динамически. В большинстве инструментов Unix «длинные строки молча срезаются». Это неприемлемо в инструменте GNU.</p></blockquote><p class="book">Это правило, возможно, единственное наиболее важное в проектировании программного обеспечения GNU — <i>никаких произвольных ограничений</i>. Все инструменты GNU должны быть способны обрабатывать произвольные объемы данных.</p><p class="book">Хотя это требование, возможно, усложняет работу программиста, оно облегчает жизнь пользователю. С одной стороны, у нас есть пользователь <code>gawk</code>, регулярно запускающий программу <code>awk</code> для более чем 650&nbsp;000 файлов (нет, это не опечатка) для сбора статистики, <code>gawk</code> заняла бы более 192 мегабайтов пространства данных, и программа работала бы в течение 7 часов. Он не смог бы запустить эту программу, используя другую реализацию <code>awk</code>.<sup><a name="anotelink22" id="anotelink22" href="#n22" title=" Эта ситуация имела место примерно в 1993 г; трюизм даже более очевиден сегодня, когда пользователи обрабатывают с помощью gawk гигабайты протокольных файлов&nbsp;— Примеч. автора.">[22]</a></sup></p><blockquote class="book"> <p class="book">Утилиты, читающие файлы, не должны удалять символы NUL или любые другие неотображаемые символы, включая символы с кодами больше 0177. Единственными здравыми исключениями были бы утилиты, специально предназначенные для связывания с определенными типами терминалов или принтеров, которые не могут обработать эти символы.</p></blockquote><p class="book">Также хорошо известно, что Emacs может редактировать любые произвольные файлы, включая файлы, содержащие двоичные данные!</p><blockquote class="book"> <p class="book">По возможности, программы должны обрабатывать должным образом последовательности байтов, представляющих многобайтные символы, используя такие кодировки, как UTF-8 и другие.<sup><a name="anotelink23" id="anotelink23" href="#n23" title=" Раздел 13.4, «Не могли бы вы произнести это для меня по буквам?», с. 521, дает обзор многобайтных символов и кодировок — Примеч. автора.">[23]</a></sup> Каждый системный вызов проверяйте на предмет возвращенной ошибки, если вы не хотите игнорировать ошибки. Включите текст системной ошибки (от <code>perror</code> или эквивалентной функции) в каждое сообщение об ошибке, возникшей при неудачном системном вызове, также, как и имя файла, если он есть, и имя утилиты. Простого «невозможно открыть foo.с» или «ошибка запуска» недостаточно.</p></blockquote><p class="book">Проверка каждого системного вызова создает устойчивость. Это еще один случай, когда жизнь программиста труднее, а пользователя легче. Подробно описанное сообщение об ошибке значительно упрощает нахождение и разрешение проблем<sup><a name="anotelink24" id="anotelink24" href="#n24" title=" Механика проверки ошибок и сообщений о них обсуждаются в разделе 4.3, «Обнаружение неправильной работы» — Примеч. автора.">[24]</a></sup>.</p><p class="book">Наконец, мы цитируем главу 1 <i>GNU Coding Standards</i>, которая обсуждает, как написать вашу программу способом, отличным от того, каким написаны программы Unix.</p><blockquote class="book"> <p class="book">Например, утилиты Unix обычно оптимизированы для минимизации использования памяти, если вы взамен хотите получить скорость, ваша программа будет сильно отличаться. Вы можете хранить весь входной файл в ядре и сканировать его там. вместо использования stdio. Используйте недавно открытый более изящный алгоритм вместо алгоритма Unix-программы. Исключите использование временных файлов. Делайте это в один проход вместо двух (мы сделали это на ассемблере) Или, напротив, сделайте упор на простоте вместо скорости. Для некоторых приложений скорость сегодняшних компьютеров делает адекватными более простые алгоритмы.</p> <p class="book">Или выберите обобщение. Например, программы Unix часто содержат статичные таблицы или строки фиксированного размера, которые создают произвольные ограничения, используйте вместо этого динамическое выделение памяти. Убедитесь, что ваша программа обрабатывает во входных файлах символы NUL и другие курьезные символы. Добавьте язык программирования для расширяемости и напишите часть программы на этом языке.</p> <p class="book">Или выделите части программы в независимо используемые библиотеки. Или используйте простой сборщик мусора вместо точного отслеживания, когда освобождать память, или используйте новую возможность GNU, такую как obstacks.</p></blockquote><p class="book">Великолепным примером того, какое отличие можно сделать в алгоритме, является GNU <code>diff</code>. Одним из первых ранних воплощений нашей системы было AT&amp;T 3B1, система с процессором МС68010, огромными двумя мегабайтами памяти и 80 мегабайтами на диске. Мы проделали (и делаем) кучу исправлений в руководстве для <code>gawk</code>, файле длиной почти 28&nbsp;000 строк (хотя в то время он был лишь в диапазоне 10&nbsp;000 строк). Обычно мы частенько использовали '<code>diff -с</code>', чтобы посмотреть на сделанные нами изменения. На этой медленной системе переключение на GNU <code>diff</code> показало ошеломительную разницу во времени появления контекста <code>diff</code>. Разница почти всецело благодаря лучшему алгоритму, который использует GNU <code>diff</code>.</p><p class="book">В последнем параграфе упоминается идея структурирования программы как независимо используемой библиотеки, с оболочкой командной строки или другим окружающим се интерфейсом. Примером этого является GDB, отладчик GNU, который реализован в виде инструмента с интерфейсом командной строки поверх отладочной библиотеки. (Разделение основных возможностей GDB от интерфейса командной строки является продолжающимся проектом). Эта реализация дает возможность создать поверх отладочных функциональных возможностей графический интерфейс отладчика.</p><a name="t17"></a><h3 class="book"> 1.4.5. Заключительные соображения по поводу «GNU Coding Standards»<br></h3><p class="book"><i>GNU Coding Standards</i> является стоящим для прочтения документом, если вы хотите разрабатывать новое программное обеспечение GNU, обмениваться существующими программами GNU или просто научиться программировать лучше. Принципы и методики, которые она поддерживает — вот что делает программное обеспечение GNU предпочитаемым выбором в сообществе Unix.</p><a name="t18"></a><h3 class="book">1.5. Пересмотренная переносимость<br> </h3> <p class="book">Переносимость является чем-то вроде Святого Грааля; всегда недостающим впоследствии, но не всегда достижимым и определенно нелегким. Есть несколько аспектов написания переносимого кода. <i>GNU Coding Standards</i> обсуждает многие из них. Но есть и другие стороны. При разработке принимайте переносимость во внимание как на высоком, так и на низком уровнях. Мы рекомендуем следующие правила:</p> <p class="book"><i>Соответствуйте стандартам</i></p> <p class="book">Хотя это может потребовать напряжения, знакомство с формальными стандартами языка, который вы используете, окупается. В частности, обратите внимание на стандарты ISO 1990 и 1999 гг. для С и стандарт 2003 г. для С++, поскольку большинство программ Linux создано на одном из этих двух языков.</p> <p class="book">В промышленности также широко поддерживается стандарт POSIX для интерфейса библиотечных и системных вызовов, хотя он и большой. Написание в соответствии с POSIX значительно повышает шансы успешного переноса вашего кода и на другие системы, помимо GNU/Linux. Этот стандарт вполне читабелен; он концентрирует в себе десятилетия опыта и хорошей практики.</p> <p class="book"><i>Выбирайте для работы лучший интерфейс</i></p> <p class="book">Если стандартный интерфейс выполняет нужную вам работу, используйте его в своем коде. Для обнаружения недоступного интерфейса используйте Autoconf, и добавьте его замещающую версию для ограниченной системы. (Например, на некоторых более старых системах отсутствует функция <code>memmove()</code>, которую довольно легко запрограммировать самому или вставить из библиотеки GLIBC).</p> <p class="book"><i>Изолируйте проблемы переносимости за новыми интерфейсами</i></p> <p class="book">Иногда вам может потребоваться выполнить специфичные для операционной системы задачи, которые можно исполнить на одних системах, но нельзя на других. (Например, на некоторых системах каждая программа должна сама раскрывать групповые символы в командной строке, вместо выполнения этой работы командным процессором.) Создайте новый интерфейс, который ничего не делает в системах, которым он не нужен, но проделывает необходимую коррекцию для систем, которые в этом нуждаются.</p> <p class="book"><i>Используйте для конфигурирования Autoconf</i></p> <p class="book">По возможности избегайте <code>#ifdef</code>. Если это невозможно, скройте его в низкоуровневом библиотечном коде. Для проверки тестов, которые должны исполняться с помощью <code>#ifdef</code>, используйте Autoconf.</p> <a name="t19"></a><h3 class="book">1.6. Рекомендуемая литература<br> </h3> <p class="book">1.&nbsp;<i>The С Programming Language</i>, 2nd edition, by Brian W. Kernighan and Dennis M. Ritchie Prentice-Hall, Englewood Cliffs, New Jersey, USA, 1989. ISBN: 0-13-110370-9<sup><a name="anotelink25" id="anotelink25" href="#n25" title=" Русский перевод Брайан Керниган, Денис Ритчи. Язык программирования Си (изд. 3-е, исправленное) Санкт- Петербург. Невский диалект, 2001 — Примеч. науч. ред.">[25]</a></sup>.</p> <p class="book">Это «библия» С, охватывающая версию стандарта С 1990 г. Это довольно сжатая книга, с большим количеством информации, помещенной в поразительно небольшое число страниц. Вам может потребоваться прочитать ее более одного раза; это стоит затраченных усилий.</p> <p class="book">2.&nbsp;<i>С, A Reference Manual</i>. 5th edition, by Samuel P. Harbison III and Guy L. Steele, Ji. Prentice-Hall, Upper Saddle River, New Jersey, USA, 2002. ISBN: 0-13-089592-X.</p> <p class="book">Это тоже классическая книга. Она охватывает оригинальный С, а также стандарты 1990 и 1999 гг. Поскольку она современна, она служит ценным дополнением к первой книге. Она охватывает многие важные темы, такие, как интернациональные типы и библиотечные функции, которых нет в книге Кернигана и Ричи.</p> <p class="book">3.&nbsp;<i>Notes on Programming in С</i>, by Rob Pike, February 21,1989 Доступна через множество веб-сайтов. Возможно, чаще всего упоминаемым местом является <code>http://www.lysator.liu.se/c/pikestyle.html</code>. (Многие другие полезные статьи доступны там же на один уровень выше: <code>http://www.lysator.liu.se/с/</code>.) Роб Пайк много лет работал в исследовательском центре Bell Labs, где были созданы С и Unix, и проводил там изыскания. Его замечания концентрируют многолетний опыт в «философию ясности в программировании», это стоит прочтения.</p> <p class="book">4.&nbsp;Различные ссылки на <code>http://www.chris-lott.org/resources/cstyle/</code>. Этот сайт включает заметки Роба Пайка и несколько статей Генри Спенсера (Henry Spencer). Особенно высокое положение занимает «Рекомендуемый стиль С и стандарты программирования» (<i>Recommended С Style and Coding Standards</i>), первоначально написанный на сайте Bell Labs Indian Hill.</p> <a name="t20"></a><h3 class="book">1.7. Резюме<br> </h3> <p class="book">•&nbsp;«Файлы и процессы» суммируют мировоззрение Linux/Unix. Трактовка файлов как потоков байтов, а устройств как файлов, и использование стандартных ввода, вывода и ошибки упрощают построение программ и унифицируют модель доступа к данным. Модель прав доступа проста, но гибка, и приложима как к файлам, так и каталогам.</p> <p class="book">•&nbsp;Процессы являются работающими программами, у которых есть связанные с ними идентификаторы пользователя и группы для проверки прав доступа, а также другие атрибуты, такие, как открытые файлы и текущий рабочий каталог.</p> <p class="book">•&nbsp;Наиболее заметным различием между стандартным С и оригинальным С является использование прототипов функций для более строгой проверки типов. Хороший программист на С должен быть способен прочесть код, написанный в оригинальном стиле, поскольку его используют многие существующие программы. Новый код должен быть написан с использованием прототипов.</p> <p class="book">•&nbsp;<i>GNU Coding Standards</i> описывает написание программ GNU. Она предусматривает многочисленные ценные методики и руководящие принципы для создания надежного, практичного программного обеспечения. Принцип «никаких произвольных ограничений» является, возможно, единственным наиболее важным из них. Этот документ является обязательным для прочтения серьезными программистами.</p> <p class="book">•&nbsp;Переносимость программ является сложной проблемой. Руководящие указания и инструментарий помогают, но в конечном счете нужен также и опыт.</p> <a name="t21"></a><h3 class="book">Упражнения<br> </h3> <p class="book">1.&nbsp;Прочтите и прокомментируйте статью Ричарда М. Столмена «Проект GNU» (Richard M. Stallman, «The GNU Project»)<sup><a name="anotelink26" id="anotelink26" href="#n26" title=" http://www.gnu.org/gnu/thegnuproject.html — Примеч. автора.">[26]</a></sup>, первоначально написанную в августе 1998&nbsp;г.</p><a name="t22"></a><h3 class="book"> Глава 2<br> Аргументы, опции и переменные окружения<br></h3> <p class="book">Первой задачей любой программы обычно является интерпретация опций и аргументов командной строки. Данная глава рассматривает, как программы С (и С++) получают аргументы своей командной строки, описывает стандартные процедуры для разбора опций и бросает взгляд на переменные окружения.</p> <a name="t23"></a><h3 class="book">2.1. Соглашения по опциям и аргументам<br> </h3> <p class="book">У слова <i>аргументы</i> есть два значения. Более техническим определением является «все 'слова' в командной строке». Например:</p><p class="book"><code>$ <b>ls main.с opts.с process.с</b></code></p><p class="book">Здесь пользователь напечатал четыре «слова». Все четыре слова сделаны доступными программе в качестве ее аргументов<sup><a name="anotelink27" id="anotelink27" href="#n27" title=" Имя команды — ls в данном примере, так же доступно программе в качестве аргумента — Примеч. науч. ред.">[27]</a></sup>.</p><p class="book">Второе определение более неформальное: аргументами являются все слова командной строки, <i>за исключением</i> имени команды. По умолчанию, оболочки Unix отделяют аргументы друг от друга <i>разделителями</i> (пробелами или символами TAB). Кавычки позволяют включать в аргументы разделитель:</p><p class="book"><code>$ <b>echo here are lots of spaces</b></code></p><p class="book"><code>here are lots of spaces /* Оболочка «съедает» пробелы */</code></p><p class="book"><code>$ <b>echo "here are lots of spaces"</b></code></p><p class="book"><code>here are lots of spaces /* Пробелы остались */</code></p><p class="book">Кавычки прозрачны для запущенной программы; <code>echo</code> никогда не видит символов двойной кавычки. (В оболочке двойные и одинарные кавычки различаются; обсуждение этих правил выходит за рамки данной книги, которая фокусируется на программировании на С.)</p><p class="book">Аргументы можно подразделить далее на <i>опции</i> и <i>операнды</i>. В предыдущих двух примерах все аргументы были операндами: файлы для <code>ls</code> и простой текст для <code>echo</code>.</p><p class="book">Опции являются специальными аргументами, которые каждая программа интерпретирует. Опции изменяют поведение программы или предоставляют программе информацию. По старому соглашению, которого (почти) всегда придерживаются, опции начинаются с черточки (т.е. дефиса, значка минус), и состоят из единственной буквы. <i>Аргументы опции</i> являются информацией, необходимой для опции, в отличие от обычных аргументов-операндов. Например, опция <code>-f</code> программы <code>fgrep</code> означает «использовать содержимое следующего файла в качестве списка строк для поиска». См. рис 2.1.</p><center><img border="0" style="spacing 9px;" src="/i/74/334674/img_1.png"></center><p class="book"><b>Рис. 2.1</b>. Компоненты командной строки</p><p class="book">Таким образом, <code>patfile</code> является не файлом данных для поиска, а предназначен для использования <code>fgrep</code> в определении списка строк, которые нужно искать.</p><a name="t24"></a><h3 class="book"> 2.1.1. Соглашения POSIX<br></h3><p class="book">Стандарт POSIX описывает ряд соглашений, которых придерживаются удовлетворяющие стандарту программы. Никто от вас не требует, чтобы ваши программы удовлетворяли этим стандартам, но это хорошая мысль сделать так: пользователи Linux и Unix по всему миру понимают и используют эти соглашения, и если вы не будете им следовать, ваши пользователи будут несчастны. (Или у вас вообще не будет пользователей!) Более того, функции, которые мы обсуждаем далее в этой главе, освобождают вас от бремени ручной реализации этих соглашений для каждой программы, которую вы пишете. Вот эти правила, перефразированные из стандарта:</p><p class="book">1.&nbsp;В имени программы должно быть не менее двух и не более девяти символов.</p><p class="book">2.&nbsp;Имена программ должны содержать лишь строчные символы и цифры.</p><p class="book">3.&nbsp;Имя опции должно быть простым буквенно-цифровым символом. Опции с множеством цифр не должны допускаться. Для производителей, реализующих утилиты POSIX, опция <code>-W</code> зарезервирована для специфичных для производителя опций.</p><p class="book">4.&nbsp;Все опции должны начинаться с символа '<code>-</code>'.</p><p class="book">5.&nbsp;Для опций, не требующих аргументов, должно быть возможно объединение нескольких опций после единственного символа '<code>-</code>'. (Например, '<code>foo -a -b -c</code>' и '<code>foo -abc</code>' должны интерпретироваться одинаково.)</p><p class="book">6.&nbsp;Когда опции все же требуется аргумент, он должен быть отделен от опции пробелом (например, '<code>fgrep -f patfile</code>').</p><p class="book">Однако, стандарт допускает историческую практику, при которой иногда опция и ее операнд могут находиться в одной строке: '<code>fgrep -fpatfile</code>'. На практике функции <code>getopt()</code> и <code>getopt_long()</code> интерпретируют '<code>-fpatfile</code>' как '<code>-f patfile</code>', а не как '<code>-f -p -a -t ...</code>'.</p><p class="book">7.&nbsp;Аргументы опций не должны быть необязательными.</p><p class="book">Это означает, что если в документации программы указано, что опции требуется аргумент, этот аргумент должен присутствовать всегда, иначе программа потерпит неудачу GNU <code>getopt()</code> все же предусматривает необязательные аргументы опций, поскольку иногда они полезны</p><p class="book">8.&nbsp;Если опция принимает аргумент, который может иметь несколько значений, программа должна получать этот аргумент в виде одной строки со значениями, разделенными запятыми или разделителем.</p><p class="book">Например, предположим, что гипотетической программе <code>myprog</code> требуется список пользователей для опции <code>-u</code>. Далее она может быть вызвана одним из двух способов:</p><p class="book"><code>myprog -u "arnold,joe,jane" /* Разделение запятыми */</code></p><p class="book"><code>myprog -u "arnold joe jane" /* Разделение пробелами */</code></p><p class="book">В таком случае вы должны самостоятельно отделить и обработать каждое значение (т.е. здесь нет стандартной процедуры), но ручная реализация обычно проста.</p><p class="book">9.&nbsp;Опции должны находиться в командной строке первыми, перед операндами. Версии <code>getopt()</code> Unix проводят в жизнь это соглашение. GNU <code>getopt()</code> по умолчанию этого не делает, хотя вы можете настроить его на это.</p><p class="book">10.&nbsp;Специальный аргумент '<code>--</code>' указывает на окончание всех опций. Все последующие аргументы командной строки рассматриваются как операнды, даже если они начинаются с черточки.</p><p class="book">11.&nbsp;Порядок, в котором приведены опции, не должен играть роли. Однако, для взаимно исключающих опций, когда одна опция перекрывает установки другой, тогда (так сказать) последняя побеждает. Если опция, имеющая аргумент, повторяется, программа должна обработать аргументы по порядку. Например, '<code>myprog -u arnold -u jane</code>' то же самое, что и '<code>myprog -u "arnold, jane"</code>'. (Вам придется осуществить это самостоятельно; <code>getopt()</code> вам не поможет.)</p><p class="book">12.&nbsp;Нормально, когда порядок аргументов имеет для программы значение. Каждая программа должна документировать такие вещи.</p><p class="book">13.&nbsp;Программы, читающие или записывающие именованные файлы, должны трактовать единственный аргумент '<code>-</code>' как означающий стандартный ввод или стандартный вывод, в зависимости от того, что подходит программе.</p><p class="book">Отметим, что многие стандартные программы не следуют всем указанным соглашениям. Главной причиной является историческая совместимость; многие такие программы предшествовали систематизации этих соглашений.</p><a name="t25"></a><h3 class="book"> 2.1.2. Длинные опции GNU<br></h3><p class="book">Как мы видели в разделе 1.4.2 «Поведение программ», программам GNU рекомендуется использовать длинные опции в форме <code>--help</code>, <code>--verbose</code> и т.д. Такие опции, поскольку они начинаются с '<code>--</code>', не конфликтуют с соглашениями POSIX. Их также легче запомнить, и они предоставляют возможность последовательности среди всех утилит GNU. (Например, <code>--help</code> является везде одним и тем же, в отличие от <code>-h</code> для «help», <code>-i</code> для «information» и т.д.) Длинные опции GNU имеют свои собственные соглашения, реализованные в функции <code>getopt_long()</code>:</p><p class="book">1.&nbsp;У программ, реализующих инструменты POSIX, каждая короткая опция (один символ) должна иметь также свой вариант в виде длинной опции.</p><p class="book">2.&nbsp;Дополнительные специфические для GNU опции не нуждаются в соответствующей короткой опции, но мы рекомендуем это сделать.</p><p class="book">3.&nbsp;Длинную опцию можно сократить до кратчайшей строки, которая остается уникальной. Например, если есть две опции <code>--verbose</code> и <code>--verbatim</code>, самыми короткими сокращениями будут <code>--verbo</code> и <code>--verba</code>.</p><p class="book">4.&nbsp;Аргументы опции отделяются от длинных опций либо разделителем, либо символом <code>=</code>. Например, <code>--sourcefile=/some/file</code> или <code>--sourcefile /some/file</code>.</p><p class="book">5.&nbsp;Опции и аргументы могут быть заинтересованы в операндах командной строки, <code>getopt_long()</code> переставляет аргументы таким образом, что сначала обрабатываются все опции, а затем все операнды доступны последовательно. (Такое поведение можно запретить.)</p><p class="book">6.&nbsp;Аргументы опций могут быть необязательными. Для таких опций считается, что аргумент присутствует, если он находится в одной строке с опцией. Это работает лишь для коротких опций. Например, если -х такая опция и дана строка '<code>foo -хYANKEES -y</code>', аргументом <code>-х</code> является '<code>YANKEES</code>'. Для '<code>foo -х -y</code>' у <code>-х</code> нет аргументов.</p><p class="book">7.&nbsp;Программы могут разрешить длинным опциям начинаться с одной черточки (Это типично для многих программ X Window.)</p><p class="book">Многое из этого станет яснее, когда позже в этой главе мы рассмотрим <code>getopt_long()</code>.</p><p class="book"><i>GNU Coding Standards</i> уделяет значительное место перечислению всех длинных и коротких опций, используемых программами GNU. Если вы пишете программу, использующую длинные опции, посмотрите, нет ли уже использующихся имен опций, которые имело бы смысл использовать и вам.</p><a name="t26"></a><h3 class="book">2.2. Базовая обработка командной строки<br> </h3> <p class="book">Программа на С получает доступ к своим аргументам командной строки через параметры <code>argc</code> и <code>argv</code>. Параметр <code>argc</code> является целым, указывающим число имеющихся аргументов, включая имя команды. Есть два обычных способа определения <code>main()</code>, отличающихся способом объявления <code>argc</code>:</p><p class="book"><code>int main(int argc, char *argv[])&nbsp; int main(int argc, char **argv)</code></p><p class="book"><code>{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</code></p><p class="book"><code>...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...</code></p><p class="book"><code>}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</code></p><p class="book">Практически между двумя этими объявлениями нет разницы, хотя первое концептуально более понятно: <code>argc</code> является массивом указателей на символы. А второе определение технически более корректно, это то, что мы используем. На рис. 2.2 изображена эта ситуация.</p><center><img border="0" style="spacing 9px;" src="/i/74/334674/img_2.png"></center><p class="book"><b>Рис. 2.2</b>. Память для <code>argc</code></p><p class="book">По соглашению, <code>argv[0]</code> является именем программы. (Детали см. в разделе 9.1.4.3. «Имена программ и <code>argv[0]</code>».) Последующие элементы являются аргументами командной строки. Последним элементом массива <code>argv</code> является указатель <code>NULL</code>.</p><p class="book"><code>argc</code> указывает, сколько имеется аргументов; поскольку в С индексы отсчитываются с нуля, выражение '<code>argv[argc] == NULL</code>' всегда верно. Из-за этого, особенно в коде для Unix, вы увидите различные способы проверки окончания списка аргументов, такие, как цикл с проверкой, что счетчик превысил <code>argc</code>, или '<code>argv[i] == 0</code>', или '<code>*argv != NULL</code>' и т.д. Они все эквивалентны.</p><a name="t27"></a><h3 class="book"> 2.2.1. Программа <code>echo</code> V7<br></h3><p class="book">Возможно, простейшим примером обработки командной строки является программа V7 <code>echo,</code> печатающая свои аргументы в стандартный вывод, разделяя их пробелами и завершая символом конца строки. Если первым аргументом является <code>-n</code>, завершающий символ новой строки опускается. (Это используется для приглашений из сценариев оболочки.) Вот код<sup><a name="anotelink28" id="anotelink28" href="#n28" title=" См. /usr/src/cmd/echo.c в дистрибутиве V7 — Примеч. автора.">[28]</a></sup>:</p><p class="book"><code>1&nbsp; #include &lt;stdio.h&gt;</code></p><p class="book"><code>2</code></p><p class="book"><code>3&nbsp; main(argc, argv) /*int main(int argc, char **argv)*/</code></p><p class="book"><code>4&nbsp; int argc;</code></p><p class="book"><code>5&nbsp; char *argv[];</code></p><p class="book"><code>6&nbsp; {</code></p><p class="book"><code>7&nbsp;&nbsp; register int i, nflg;</code></p><p class="book"><code>8</code> </p><p class="book"><code>9&nbsp;&nbsp; nflg = 0;</code></p><p class="book"><code>10&nbsp; if (argc &gt; 1 &amp;&amp; argv[1][0] == &amp;&amp; argv[1][1] == 'n') {</code></p><p class="book"><code>11&nbsp;&nbsp; nflg++;</code></p><p class="book"><code>12&nbsp;&nbsp;&nbsp;argc--;</code></p><p class="book"><code>13&nbsp;&nbsp;&nbsp;argv++;</code></p><p class="book"><code>14&nbsp;&nbsp;}</code></p><p class="book"><code>15&nbsp;&nbsp;for (i=1; i&lt;argc; i++) {</code></p><p class="book"><code>16&nbsp;&nbsp; fputs(argv[i], stdout);</code></p><p class="book"><code>17&nbsp;&nbsp; if (i &lt; argc-1)</code></p><p class="book"><code>18&nbsp;&nbsp; putchar(' ');</code></p><p class="book"><code>19&nbsp;&nbsp;}</code></p><p class="book"><code>20&nbsp;&nbsp;if (nflg == 0)</code></p><p class="book"><code>21&nbsp;&nbsp; putchar('\n');</code></p><p class="book"><code>22&nbsp;&nbsp;exit(0);</code></p><p class="book"><code>23&nbsp;}</code></p><p class="book">Всего 23 строки! Здесь есть два интересных момента. Во-первых, уменьшение <code>argc</code> и одновременное увеличение <code>argv</code> (строки 12 и 13) являются обычным способом пропуска начальных аргументов. Во-вторых, проверка наличия <code>-n</code> (строка 10) является упрощением. <code>-no-newline-at-the-end</code> также работает. (Откомпилируйте и проверьте это!)</p><p class="book">Ручной разбор опций обычен для кода V7, поскольку функция <code>getopt()</code> не была еще придумана.</p><p class="book">Наконец, здесь и в других местах по всей книге, мы видим использование ключевого слова register. Одно время это ключевое слово давало компилятору подсказку, что данная переменная должна по возможности размещаться в регистре процессора. Теперь это ключевое слово устарело; современные компиляторы все основывают размещение переменных в регистрах на анализе исходного кода, игнорируя ключевое слово <code>register</code>. Мы решили оставить использующий это слово код, как есть, но вы должны знать, что оно больше не имеет реального применения.<sup><a name="anotelink29" id="anotelink29" href="#n29" title=" Когда мы спросили Джима Мейеринга (Jim Meyering), сопроводителя Coreulils, о наличии register в GNU Coreutils, он дал нам интересный ответ. Он удаляет эти слова при изменении кода, но в остальных случаях оставляет их на месте, чтобы облегчить интеграцию сделанных изменений с существующими версиями — Примеч. автора.">[29]</a></sup></p><a name="t28"></a><h3 class="book">2.3. Разбор опций: <code>getopt()</code> и <code>getopt_long()</code><br> </h3> <p class="book">Примерно в 1980-х группа поддержки Unix для System III в AT&amp;T заметила, что каждая программа Unix использовала для разбора аргументов свои собственные методики. Чтобы облегчить работу пользователей и программистов, они разработали большинство из перечисленных ранее соглашений. (Хотя изложение в System III справки для <i>intro</i>(1) значительно менее формально, чем в стандарте POSIX.)</p><p class="book">Группа поддержки Unix разработала также функцию <code>getopt()</code>, вместе с несколькими внешними переменными, чтобы упростить написание кода, придерживающегося стандартных соглашений. Функция GNU <code>getopt_long()</code> предоставляет совместимую с <code>getopt()</code> версию, а также упрощает разбор длинных опций в описанной ранее форме.</p><a name="t29"></a><h3 class="book"> 2.3.1. Опции с одним символом<br></h3><p class="book">Функция <code>getopt()</code> объявлена следующим образом:</p><p class="book"><code>#include &lt;unistd.h&gt; /*POSIX*/</code></p><br><p class="book"><code>int getopt(int argc, char *const argv[], const char *optstring);</code></p><p class="book"><code>extern char *optarg;</code></p><p class="book"><code>extern int optind, opterr, optopt;</code></p><p class="book">Аргументы <code>argc</code> и <code>argv</code> обычно передаются непосредственно от <code>main()</code>. <code>optstring</code> является строкой символов опций. Если за какой-либо буквой в строке следует двоеточие, эта опция ожидает наличия аргумента.</p><p class="book">Для использования <code>getopt()</code> вызывайте ее повторно из цикла <code>while</code> до тех пор, пока она не вернет <code>-1</code>. Каждый раз, обнаружив действительный символ опции, функция возвращает этот символ. Если опция принимает аргумент, указатель на него помещается в переменную <code>optarg</code>. Рассмотрим программу, принимающую опцию <code>-а</code> без аргумента и опцию <code>-b</code> с аргументом:</p><p class="book"><code>int ос; /* символ опции */</code></p><p class="book"><code>char *b_opt_arg;</code></p><p class="book"><code>while ((ос = getopt(argc, argv, "ab:")) != -1) {</code></p><p class="book"><code>&nbsp;switch (oc) {</code></p><p class="book"><code>&nbsp;case 'a':</code></p><p class="book"><code>&nbsp; /* обработка -а, установить соответствующий флаг */</code></p><p class="book"><code>&nbsp; break;</code></p><p class="book"><code>&nbsp;case 'b':</code></p><p class="book"><code>&nbsp; /* обработка -b, получить значение аргумента из optarg */</code></p><p class="book"><code>&nbsp; b_opt_arg = optarg;</code></p><p class="book"><code>&nbsp; break;</code></p><p class="book"><code>&nbsp;case ':':</code></p><p class="book"><code>&nbsp; ... /* обработка ошибок, см. текст */</code></p><p class="book"><code>&nbsp;case '?':</code></p><p class="book"><code>&nbsp;default:</code></p><p class="book"><code>&nbsp; ... /* обработка ошибок, см. текст */</code></p><p class="book"><code>&nbsp;}</code></p><p class="book"><code>}</code></p><p class="book">В ходе работы <code>getopt()</code> устанавливает несколько переменных, контролирующих обработку ошибок:</p><p class="book"><code>char *optarg</code></p><p class="book">Аргумент для опции, если она принимает аргумент.</p><p class="book"><code>int optind</code></p><p class="book">Текущий индекс в <code>argv</code>. Когда цикл <code>loop</code> завершается, оставшиеся операнды находятся с <code>argv[optind]</code> по <code>argv[argc-1]</code>. (Помните, что '<code>argv [argc] ==NULL</code>'.)</p><p class="book"><code>int opterr</code></p><p class="book">Когда эта переменная не равна нулю (значение по умолчанию), <code>getopt()</code> печатает свои собственные сообщения для недействительных опций или отсутствующих аргументов опций.</p><p class="book"><code>int optopt</code></p><p class="book">Когда находится недействительный символ опции, <code>getopt()</code> возвращает либо '<code>?</code>', либо '<code>:</code>' (см ниже), a <code>optopt</code> содержит обнаруженный недействительный символ.</p><p class="book">Люди есть люди, программы неизбежно будут иногда вызываться неправильно либо с недействительной опцией, либо с отсутствующим аргументом опции. Обычно в таких случаях <code>getopt()</code> выводит свои собственные сообщения и возвращает символ '<code>?</code>'. Однако, вы можете изменить такое поведение двумя способами.</p><p class="book">Во-первых, записав 0 в <code>opterr</code> перед вызовом <code>getopt()</code>, можно заставить <code>getopt()</code> не предпринимать при обнаружении проблем никаких действий.</p><p class="book">Во-вторых, если <i>первый</i> символ в <code>optstring</code> является двоеточием, <code>getopt()</code> не предпринимает никаких действий и возвращает другой символ в зависимости от ошибки следующим образом:</p><p class="book"><i>Неверная опция</i></p><p class="book"><code>getopt()</code> возвращает '<code>?</code>', a <code>optopt</code> содержит неверный символ опции (Это обычное поведение).</p><p class="book"><i>Отсутствует аргумент опции</i></p><p class="book"><code>getopt()</code> возвращает '<code>:</code>'. Если первый символ <code>optstring</code> не является двоеточием, <code>getopt()</code> возвращает '<code>?</code>', делая этот случай неотличимым от случая неверной опции.</p><p class="book">Таким образом, помещение в качестве первого символа <code>optstring</code> двоеточия является хорошей мыслью, поскольку это позволяет различать «неверную опцию» и «отсутствующий аргумент опции». Расплатой за это является то, что <code>getopt()</code> в этом случае также не предпринимает никаких действий, заставляя вас выводить собственные сообщения об ошибках. Вот предыдущий пример, на этот раз с обработкой ошибок:</p><p class="book"><code>int ос; /* символ опции */</code></p><p class="book"><code>char *b_opt_arg;</code></p><p class="book"><code>while ((ос = getopt(argc, argv, ":ab:")) != -1) {</code></p><p class="book"><code>&nbsp;switch (oc) {</code></p><p class="book"><code>&nbsp;case 'a':</code></p><p class="book"><code>&nbsp; /* обработка -a, установка соответствующего флага */</code></p><p class="book"><code>&nbsp; break;</code></p><p class="book"><code>&nbsp;case 'b':</code></p><p class="book"><code>&nbsp; /* обработка -b, получение значения аргумента из optarg */</code></p><p class="book"><code>&nbsp; b_opt_arg = optarg;</code></p><p class="book"><code>&nbsp;&nbsp;break;</code></p><p class="book"><code>&nbsp;case ':':</code></p><p class="book"><code>&nbsp; /* отсутствует аргумент опции */</code></p><p class="book"><code>&nbsp; fprintf(stderr, "%s: option '-%c' requires an argument\n",</code></p><p class="book"><code>&nbsp;&nbsp; argv[0], optopt);</code></p><p class="book"><code>&nbsp; break;</code></p><p class="book"><code>&nbsp;case '?':</code></p><p class="book"><code>&nbsp;default:</code></p><p class="book"><code>&nbsp; /* недействительная опция */</code></p><p class="book"><code>&nbsp; fprintf(stderr, "%s: option '-%c' is invalid: ignored\n",</code></p><p class="book"><code>&nbsp;&nbsp; argv[0], optopt);</code></p><p class="book"><code>&nbsp; break;</code></p><p class="book"><code>&nbsp;}</code></p><p class="book"><code>}</code></p><p class="book">Замечание о соглашениях по именованию флагов или опций: в большом количестве кода для Unix используются имена в виде <code>xflg</code> для любого данного символа опции <i>x</i> (например, <code>nflg</code> в <code>echo</code> V7; обычным является также <code>xflag</code>). Это может быть замечательным для авторе программы, который без проверки документации знает, что означает опция <i>x</i>. Но это не подходит для кого-то еще, кто пытается прочесть код и не знает наизусть значений всех символов опций. Гораздо лучше использовать имена, передающие смысл опции, как <code>no_newline</code> для опции <code>-n</code> echo.</p><a name="t30"></a><h3 class="book"> 2.3.2. GNU <code>getopt()</code> и порядок опций<br></h3><p class="book">Стандартная функция <code>getopt()</code> прекращает поиск опций, как только встречает аргумент командной строки, который не начинается с GNU <code>getopt()</code> отличается: она просматривает в поисках опций всю командную строку. По мере продвижения она переставляет элементы <code>argv</code>, так что после ее завершения все опции оказываются переставленными в начало, и код, продолжающий разбирать аргументы с <code>argv[optind]</code> до <code>argv[argc-1]</code>, работает правильно. Во всех случаях специальный аргумент '<code>--</code>' завершает сканирование опций.</p><p class="book">Вы можете изменить поведение по умолчанию, использовав в <code>optstring</code> специальный первый символ следующим образом:</p><p class="book"><code>optstring[0] == '+'</code></p><p class="book">GNU <code>getopt()</code> ведет себя, как стандартная <code>getopt()</code>; она возвращает опции по мере их обнаружения, останавливаясь на первом аргументе, не являющемся опцией. Это работает также в том случае, если в окружении присутствует строка <code>POSIXLY_CORRECT</code>.</p><p class="book"><code>optstring[0] == '-'</code></p><p class="book">GNU <code>getopt()</code> возвращает каждый аргумент командной строки независимо от того, представляет он аргумент или нет. В этом случае для каждого такого аргумента функция возвращает целое 1, а указатель на соответствующую строку помещает в <code>optarg</code>.</p><p class="book">Как и для стандартной <code>getopt()</code>, если первым символом <code>optstring</code> является '<code>:</code>', GNU <code>getopt()</code> различает «неверную опцию» и «отсутствующий аргумент опции», возвращая соответственно '<code>?</code>' или '<code>:</code>'. Символ '<code>:</code>' в <code>optstring</code> может быть вторым символом, если первым символом является '<code>+</code>' или '<code>-</code>'.</p><p class="book">Наконец, если за символом опции в <code>optstring</code> следуют <i>два</i> двоеточия, эта опция может иметь необязательный аргумент. (Быстро повторите это три раза!) Такой аргумент считается присутствующим, если он находится в том же элементе <code>argv</code>, что и сама опция, и отсутствующим в противном случае. В случае отсутствия аргумента GNU <code>getopt()</code> возвращает символ опции, а в <code>optarg</code> записывает NULL. Например, пусть имеем:</p><p class="book"><code>while ((с = getopt(argc, argv, "ab::")) != -1)</code></p><p class="book"><code>...</code></p><p class="book">для <code>-bYANKEES</code>, возвращаемое значение будет '<code>b</code>', a <code>optarg</code> указывает на «<code>YANKEES</code>», тогда как для <code>-b</code> или '<code>-b YANKEES</code>' возвращаемое значение будет все то же '<code>b</code>', но в <code>optarg</code> будет помещен NULL. В последнем случае «<code>YANKEES</code>» представляет отдельный аргумент командной строки.</p><a name="t31"></a><h3 class="book"> 2.3.3. Длинные опции<br></h3> <p class="book">Функция <code>getopt_long()</code> осуществляет разбор длинных опций в описанном ранее виде. Дополнительная процедура <code>getopt_long_only()</code> работает идентичным образом, но она используется для программ, в которых все опции являются длинными и начинаются с единичного символа '<code>-</code>'. В остальных случаях обе функции работают точно так же, как более простая функция GNU <code>getopt()</code>. (Для краткости, везде, где мы говорим «<code>getopt_long()</code>», можно было бы сказать «<code>getopt_long()</code> и <code>getopt_long_only()</code>».) Вот объявления функций из справки getopt(3) GNU/Linux:</p> <p class="book"><code>#include &lt;getopt.h&gt; /* GLIBC */</code></p> <br> <p class="book"><code>int getopt_long(int argc, char *const argv[],</code></p> <p class="book"><code>&nbsp;const char *optstring,</code></p> <p class="book"><code>&nbsp;const struct option *longopts, int *longindex);</code></p> <p class="book"><code>int getopt_long_only(int argc, char *const argv[],</code></p> <p class="book"><code>&nbsp;const char *optstring,</code></p> <p class="book"><code>&nbsp;const struct option *longopts, int *longindex);</code></p> <p class="book">Первые три аргумента те же, что и в <code>getopt()</code>. Следующая опция является указателем на массив <code>struct option</code>, который мы назовем <i>таблицей длинных опций</i> и который вскоре опишем. Параметр <code>longindex</code>, если он не установлен в NULL, указывает на переменную, в которую помешается индекс обнаруженной длинной опции в <code>longopts</code>. Это полезно, например, при диагностике ошибок.</p> <a name="t32"></a><h3 class="book">2.3.3.1. Таблица длинных опций<br> </h3> <p class="book">Длинные опции описываются с помощью массива структур <code>struct option</code>. Структура <code>struct option</code> определена в <code>&lt;getopt.h&gt;</code>; она выглядит следующим образом:</p> <p class="book"><code>struct option {</code></p> <p class="book"><code>&nbsp;const char *name;</code></p> <p class="book"><code>&nbsp;int has_arg;</code></p> <p class="book"><code>&nbsp;int *flag;</code></p> <p class="book"><code>&nbsp;int val;</code></p> <p class="book"><code>};</code></p> <p class="book">Элементы структуры следующие:</p> <p class="book"><code>const char *name</code></p> <p class="book">Это имя опции <i>без</i> предшествующих черточек, например, «<code>help</code>» или «<code>verbose</code>».</p> <p class="book"><code>int has_arg</code></p> <p class="book">Переменная описывает, имеет ли длинная опция аргумент, и если да, какого вида этот аргумент. Значение должно быть одно из представленных в табл. 2.1. Макроподстановки являются некоторыми символическими именами для числовых значений, приведенных в таблице. Хотя числовые значения тоже работают, макроподстановки гораздо легче читать, и вы должны их использовать вместо соответствующих чисел в любом коде, который пишете.</p> <p class="book"><code>int *flag</code></p> <p class="book">Если этот указатель равен NULL, <code>getopt_long()</code> возвращает значение поля <code>val</code> структуры. Если он не равен NULL, переменная, на которую он указывает, заполняется значением <code>val</code>, a <code>getopt_long()</code> возвращает 0. Если <code>flag</code> не равен NULL, но длинная опция отсутствует, указанная переменная не изменяется.</p> <p class="book"><code>int val</code></p> <p class="book">Если длинная опция обнаружена, это возвращаемое значение или значение для загрузки в <code>*flag</code>, если <code>flag</code> не равен NULL. Обычно, если <code>flag</code> не равен NULL, <code>val</code> является значением true/false, вроде 1 или 0. С другой стороны, если <code>flag</code> равен NULL, <code>val</code> обычно содержит некоторую символьную константу. Если длинная опция соответствует короткой, эта символьная константа должна быть той же самой, которая появляется в аргументе <code>optstring</code> для этой опции. (Все это станет вскоре ясно, когда мы рассмотрим несколько примеров.)</p> <br> <p class="book"><b>Таблица 2.1</b>. Значения для <code>has_arg</code></p> <table><tbody><tr align="left"> <th align="left" valign="top">Макроподстановка</th> <th align="left" valign="top">Числовое значение</th> <th align="left" valign="top">Смысл</th></tr><tr align="left"> <td align="left" valign="top"><code>no_argument</code></td> <td align="left" valign="top">0</td> <td align="left" valign="top">Опция не принимает аргумент</td></tr><tr align="left"> <td align="left" valign="top"><code>required_argument</code></td> <td align="left" valign="top">1</td> <td align="left" valign="top">Опции требуется аргумент</td></tr><tr align="left"> <td align="left" valign="top"><code>optional_argument</code></td> <td align="left" valign="top">2</td> <td align="left" valign="top">Аргумент опции является необязательным</td></tr> </tbody></table> <p class="book">У каждой длинной опции есть один такой элемент с соответствующими заполненными значениями. В последнем элементе массива все значения должны быть равны нулю. Нет необходимости сортировать массив: <code>getopt_long()</code> осуществляет линейный поиск. Однако, сортировка его по длинным именам может упростить его чтение для программиста.</p> <p class="book">При первой встрече использование <code>flag</code> и <code>val</code> кажется сбивающим с толку. Давайте сделаем на время шаг назад и рассмотрим, почему это работает именно таким способом В большинстве случаев, обработка опций заключается в установке значений различных флаговых переменных при обнаружении различных символов опций, наподобие этого:</p> <p class="book"><code>while ((с = getopt(argc, argv, ":af:hv")) != -1) {</code></p> <p class="book"><code>&nbsp;switch (с) {</code></p> <p class="book"><code>&nbsp;case 'a':</code></p> <p class="book"><code>&nbsp; do_all = 1;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 'f':</code></p> <p class="book"><code>&nbsp; myfile = optarg;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 'h':</code></p> <p class="book"><code>&nbsp; do_help = 1;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 'v':</code></p> <p class="book"><code>&nbsp; do_verbose = 1;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;... /* Здесь обработка ошибок */</code></p> <p class="book"><code>&nbsp;}</code></p> <p class="book"><code>}</code></p> <p class="book">Когда <code>flag</code> не равен NULL, <code>getopt_long()</code> <i>устанавливает значения переменных за вас</i>. Это снижает число операторов <code>case</code> в предыдущем <code>switch</code> с трех до одного. Вот пример таблицы длинных опций и код для работы с ней:</p> <p class="book"><code>int do_all, do_help, do_verbose; /* флаговые переменные */</code></p> <p class="book"><code>char *my_file;</code></p> <p class="book"><code>struct option longopts[] = {</code></p> <p class="book"><code>&nbsp;{ "all", no_argument, &amp;do_all, 1 },</code></p> <p class="book"><code>&nbsp;{ "file", required_argument, NULL, 'f' },</code></p> <p class="book"><code>&nbsp;{ "help", no_argument, &amp;do_help, 1 },</code></p> <p class="book"><code>&nbsp;{ "verbose", no_argument, &amp;do_verbose, 1 },</code></p> <p class="book"><code>&nbsp;{ 0, 0, 0, 0 }</code></p> <p class="book"><code>};</code></p> <br> <p class="book"><code>while ((с =</code></p> <p class="book"><code>&nbsp;getopt_long(argc, argv, ":f:", longopts, NULL)) != -1) {</code></p> <p class="book"><code>&nbsp;switch (c) {</code></p> <p class="book"><code>&nbsp;case 'f':</code></p> <p class="book"><code>&nbsp; myfile = optarg;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 0:</code></p> <p class="book"><code>&nbsp; /* getopt_long() устанавливает значение переменной,</code></p> <p class="book"><code>&nbsp;&nbsp; &nbsp; просто продолжить выполнение */</code></p> <p class="book"><code>&nbsp;&nbsp;break;</code></p> <p class="book"><code>&nbsp;... /* Здесь обработка ошибок */</code></p> <p class="book"><code>&nbsp;}</code></p> <p class="book"><code>}</code></p> <p class="book">Обратите внимание, что значение, переданное аргументу <code>optstring</code>, не содержит больше '<code>a</code>', '<code>h</code>' или '<code>v</code>'. Это означает, что соответствующие короткие опции неприемлемы. Чтобы разрешить как длинные, так и короткие опции, вам придется восстановить в <code>switch</code> соответствующие <code>case</code> из первого примера.</p> <p class="book">На практике следует писать свои программы так, чтобы у каждой короткой опции была также соответствующая длинная опция. В этом случае проще всего установить в <code>flag</code> NULL, а в <code>val</code> соответствующий единичный символ.</p> <a name="t33"></a><h3 class="book">2.3.3.2. Длинные опции в стиле POSIX<br> </h3> <p class="book">Стандарт POSIX резервирует опцию <code>-W</code> для специфических для производителя возможностей. Поэтому по определению <code>-W</code> непереносимо между различными системами.</p> <p class="book">Если за <code>W</code> в аргументе <code>optstring</code> следует точка с запятой (обратите внимание не двоеточие), <code>getopt_long()</code> рассматривает <code>-Wlongopt</code> так же, как <code>--longopt</code>. Соответственно в предыдущем примере измените вызов следующим образом:</p> <p class="book"><code>while ((с =</code></p> <p class="book"><code>&nbsp;getopt_long(argc, argv, ":f:W;", longopts, NULL)) != -1) {</code></p> <p class="book">С этим изменением <code>-Wall</code> является тем же, что и <code>--all</code>, a <code>-Wfile=myfile</code> тем же, что <code>--file=myfile</code>. Использование точки с запятой позволяет программе использовать при желании <code>-W</code> в качестве обычной опции. (Например, GCC использует ее как нормальную опцию, тогда как <code>gawk</code> использует ее для совместимости с POSIX.)</p> <a name="t34"></a><h3 class="book">2.3.3 3. Сводка возвращаемых значений <code>getopt_long()</code><br> </h3> <p class="book">Теперь должно быть ясно, что <code>getopt_long()</code> предоставляет гибкий механизм для разбора опций. В табл. 2.2 приведена сводка всех возможных возвращаемых значений функции и их значение.</p> <br> <p class="book"><b>Таблица 2.2</b>. Возвращаемые значения <code>getopt_long()</code></p> <table><tbody><tr align="left"> <th align="left" valign="top">Возвращаемый код</th> <th align="left" valign="top">Значение</th></tr><tr align="left"> <td align="left" valign="top">0</td> <td align="left" valign="top"><code>getopt_long()</code> установила флаг, как указано в таблице длинных опций</td></tr><tr align="left"> <td align="left" valign="top">1</td> <td align="left" valign="top"><code>optarg</code> указывает на простой аргумент командной строки</td></tr><tr align="left"> <td align="left" valign="top">'?'</td> <td align="left" valign="top">Недействительная опция</td></tr><tr align="left"> <td align="left" valign="top">' '</td> <td align="left" valign="top">Отсутствующий аргумент опции</td></tr><tr align="left"> <td align="left" valign="top">'<i>x</i>'</td> <td align="left" valign="top">Символ опции '<i>x</i>'</td></tr><tr align="left"> <td align="left" valign="top">-1</td> <td align="left" valign="top">Конец опций</td></tr> </tbody></table> <br> <p class="book">Наконец, мы улучшим предыдущий пример кода, показав оператор <code>switch</code> полностью:</p> <p class="book"><code>int do_all, do_help, do_verbose; /* флаговые переменные */</code></p> <p class="book"><code>char *myfile, *user; /* файл ввода, имя пользователя */</code></p> <p class="book"><code>struct option longopts[] = {</code></p> <p class="book"><code>&nbsp;{ "all", no_argument, &amp;do_all, 1 },</code></p> <p class="book"><code>&nbsp;{ "file", required_argument, NULL, 'f'},</code></p> <p class="book"><code>&nbsp;{ "help", no_argument, &amp;do_help, 1 },</code></p> <p class="book"><code>&nbsp;{ "verbose", no_argument, &amp;do_verbose, 1 },</code></p> <p class="book"><code>&nbsp;{ "user" , optional_argument, NULL, 'u'},</code></p> <p class="book"><code>&nbsp;{ 0, 0, 0, 0 }</code></p> <p class="book"><code>};</code></p> <p class="book"><code>...</code></p> <p class="book"><code>while((c=getopt_long(argc, argv, ":ahvf:u::W;", longopts, NULL)) != -1) {</code></p> <p class="book"><code>&nbsp;switch (c) {</code></p> <p class="book"><code>&nbsp;case 'a':</code></p> <p class="book"><code>&nbsp; do_all = 1;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 'f':</code></p> <p class="book"><code>&nbsp; myfile = optarg;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 'h':</code></p> <p class="book"><code>&nbsp; do_help = 1;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 'u':</code></p> <p class="book"><code>&nbsp; if (optarg != NULL)</code></p> <p class="book"><code>&nbsp;&nbsp; user = optarg;</code></p> <p class="book"><code>&nbsp; else</code></p> <p class="book"><code>&nbsp;&nbsp; user = "root";</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 'v':</code></p> <p class="book"><code>&nbsp; do_verbose = 1;</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case 0:</code></p> <p class="book"><code>&nbsp; /* getopt_long() установил переменную, просто продолжить */</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>#if 0</code></p> <p class="book"><code>&nbsp;case 1:</code></p> <p class="book"><code>&nbsp; /*</code></p> <p class="book"><code>&nbsp;&nbsp; * Используйте этот case, если getopt_long() должна</code></p> <p class="book"><code>&nbsp;&nbsp; * просмотреть все аргументы. В этом случае добавьте к</code></p> <p class="book"><code>&nbsp;&nbsp; * optstring ведущий * символ '-'. Действительный код,</code></p> <p class="book"><code>&nbsp;&nbsp; * если он есть, работает здесь.</code></p> <p class="book"><code>&nbsp;&nbsp; */</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>#endif</code></p> <p class="book"><code>&nbsp;case ':': /* отсутствует аргумент опции */</code></p> <p class="book"><code>&nbsp; fprintf(stderr, "%s: option '-%c' requires an argument\n",</code></p> <p class="book"><code>&nbsp;&nbsp; argv[0], optopt);</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;case '?':</code></p> <p class="book"><code>&nbsp;default: /* недействительная опция */</code></p> <p class="book"><code>&nbsp; fprintf(stderr, "%s: option '-%c' is invalid: ignored\n",</code></p> <p class="book"><code>&nbsp;&nbsp; argv[0], optopt);</code></p> <p class="book"><code>&nbsp; break;</code></p> <p class="book"><code>&nbsp;}</code></p> <p class="book"><code>}</code></p> <p class="book">В своих программах вы можете захотеть сделать для каждого символа опции комментарии, объясняющие их значение. Однако, если вы использовали описательные имена переменных для каждого символа опции, комментарии уже не так нужны. (Сравните <code>do_verbose</code> и <code>vflag</code>.)</p> <a name="t35"></a><h3 class="book">2.3.3.4. GNU <code>getopt()</code> или <code>getopt_long()</code> в программах пользователей<br> </h3> <p class="book">Вы можете захотеть использовать в своих программах GNU <code>getopt()</code> или <code>getopt_long()</code> и заставить их работать на не-Linux системах/ Это нормально; просто скопируйте исходные файлы из программы GNU или из CVS архива библиотеки С GNU (GLIBC)<sup><a name="anotelink30" id="anotelink30" href="#n30" title=" См. http://sources.redhat.com — Примеч. автора.">[30]</a></sup>. Исходные файлы <code>getopt.h</code>, <code>getopt.с</code> и <code>getopt1.c</code>. Они лицензированы на условиях меньшей общедоступной лицензии (Lesser General Public License) GNU, которая позволяет включать библиотечные функции даже в патентованные программы. Вы должны включить в свою программу копию файла <code>COPYING.LIB</code> наряду с файлами <code>getopt.h</code>, <code>getopt.с</code> и <code>getopt1.с</code>.</p> <p class="book">Включите исходные файлы в свой дистрибутив и откомпилируйте их с другими исходными файлами. В исходном коде, вызывающем <code>getopt_long()</code>, используйте '<code>#include &lt;getopt.h&gt;</code>', а не '<code>#include "getopt.h"</code>'. Затем, при компилировании, добавьте к командной строке компилятора С <code>-I</code>. Таким способом сначала будет найдена локальная копия заголовочного файла.</p> <p class="book">Вы можете поинтересоваться: «Вот так, я уже использую GNU/Linux. Почему я должен включать <code>getopt_long()</code> в свой исполняемый модуль, увеличивая его размер, если процедура уже находится в библиотеке С?» Это хороший вопрос. Однако, здесь не о чем беспокоиться. Исходный код построен так, что если он компилируется на системе, которая использует GLIBC, откомпилированные файлы не будут содержать никакого кода! Вот подтверждение на нашей системе:</p> <p class="book"><code>$ <b>uname -а</b> /* Показать имя и тип системы */</code></p> <p class="book"><code>Linux example 2.4.18-14 #1 Wed Sep 4 13:35:50 EDT 2002 i686 i686 i386 GNU/Linux</code></p> <p class="book"><code>$ <b>ls -l getopt.о getopt1.о</b> /* Показать размеры файлов */</code></p> <p class="book"><code>-rw-r--r-- 1 arnold devel 9836 Mar 24 13:55 getopt.о</code></p> <p class="book"><code>-rw-r--r-- 1 arnold devel 10324 Mar 24 13:55 getopt1.о</code></p> <p class="book"><code>$ <b>size getopt.о getopt1.о</b> /* Показать включенные в исполняемый</code></p> <p class="book"><code>модуль размеры */</code></p> <p class="book"><code>text data bss dec hex filename</code></p> <p class="book"><code>0 0 0 0 0 getopt.о</code></p> <p class="book"><code>0 0 0 0 0 getopt1.о</code></p> <p class="book">Команда <code>size</code> печатает размеры различных составных частей двоичного объекта или исполняемого файла. Мы объясним вывод в разделе 3.1 «Адресное пространство Linux/Unix». Что важно понять прямо сейчас, это то, что несмотря на ненулевой размер самих файлов, они не вносят никакого вклада в конечный исполняемый модуль. (Думаем, это достаточно ясно.)</p><a name="t36"></a><h3 class="book">2.4. Переменные окружения<br> </h3> <p class="book"><i>Окружение</i> представляет собой набор пар вида '<code><i>имя</i>=<i>значение</i></code>' для каждой программы. Эти пары называются <i>переменными окружения</i>. Каждое имя состоит от одной до любого числа буквенно-цифровых символов или символов подчеркивания ('<code>_</code>'), но имя не может начинаться с цифры. (Это правило контролируется оболочкой; С API может помешать в окружение все, что захочет, за счет возможного запутывания последующих программ.)</p><p class="book">Переменные окружения часто используются для управления поведением программ. Например, если в окружении существует <code>POSIXLY_CORRECT</code>, многие программы запрещают расширения или историческое поведение, которые несовместимы со стандартом POSIX.</p><p class="book">Вы можете решить использовать (и должны задокументировать) переменные окружения для управления поведением вашей программы. Например, вы можете вместо аргумента командной строки использовать для опций отладки переменную окружения. Преимуществом использования переменных окружения является то, что пользователи могут установить их в своем загрузочном файле и не беспокоиться больше постоянным набором определенных опций в командной строке.</p><p class="book">Конечно, недостатком использования переменных окружения является то, что они могут молча изменять поведение программы. Джим Мейеринг (Jim Meyering), сопроводитель Coreutils, выразил это таким образом:</p><blockquote class="book"> <p class="book">Они упрощают пользователю настройку программы без изменения способа ее вызова. Это может быть как благословением, так и проклятием. Если вы пишете сценарий, который зависит от значения определенной переменной окружения, а затем этот сценарий использует еще кто-то, у кого нет таких же установок окружения, он легко может потерпеть неудачу (или, что еще хуже, молча выдать неверные результаты).</p></blockquote><a name="t37"></a><h3 class="book"> 2.4.1. Функции управления окружением<br></h3><p class="book">Несколько функций позволяют получать значения переменных окружения, изменять эти значения или удалять их. Вот соответствующие объявления:</p><p class="book"><code>#include &lt;stdlib.h&gt;</code></p><br><p class="book"><code>char *getenv(const char *name);</code></p><p class="book"><code>/* ISO С: Получить переменную</code></p><p class="book"><code>&nbsp;&nbsp; окружения */</code></p><p class="book"><code>int setenv(const char *name, /* POSIX: Установить переменную */</code></p><p class="book"><code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const char *value, /* окружения */</code></p><p class="book"><code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int overwrite);</code></p><p class="book"><code>int putenv(char *string); /* XSI: Установить переменную</code></p><p class="book"><code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; окружения, использует строку */</code></p><p class="book"><code>void unsetenv(const char *name); /* POSIX: Удалить переменную</code></p><p class="book"><code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; окружения */</code></p><p class="book"><code>int clearenv(void); /* Общее: очистить все окружение */</code></p><p class="book">Функция <code>getenv()</code> — та, которую вы будете использовать в 99% случаев. Ее аргументом является имя переменной окружения, которую нужно искать, такое, как «<code>НОМЕ</code>» или «<code>PATH</code>». Если переменная существует, <code>getenv()</code> возвращает указатель на строковое значение. Если нет, возвращается <code>NULL</code>. Например:</p><p class="book"><code>char *pathval;</code></p><p class="book"><code>/* Поиск PATH; если нет, использовать значение</code></p><p class="book"><code>&nbsp;&nbsp; по умолчанию */</code></p><p class="book"><code>if ((pathval = getenv("PATH")) == NULL)</code></p><p class="book"><code>&nbsp;pathval = "/bin:/usr/bin:/usr/ucb";</code></p><p class="book">Иногда переменная окружения существует, но с пустым значением. В этом случае возвращаемое значение не равно <code>NULL</code>, но первый символ, на которую оно указывает, будет нулевым байтом, который в С является символом конца строки, '<code>\0</code>'. Ваш код должен позаботиться проверить, что возвращаемое значение не равно NULL. Если оно не <code>NULL</code>, необходимо также проверить, что строка не пустая, если вы хотите для чего-то использовать значение переменной. В любом случае, не используйте возвращенное значение слепо.</p><p class="book">Для изменения переменной окружения или добавления к окружению еще одной используется <code>setenv()</code>:</p><p class="book"><code>if (setenv("PATH", "/bin:/usr/bin:/usr/ucb", 1) != 0) {</code></p><p class="book"><code>&nbsp;/* обработать ошибку */</code></p><p class="book"><code>}</code></p><p class="book">Возможно, что переменная уже существует в окружении. Если третий аргумент равен true (не ноль), новое значение затирает старое. В противном случае, предыдущее значение не меняется. Возвращаемое значение равно -1, если для новой переменной не хватило памяти, и 0 в противном случае. <code>setenv()</code> для сохранения в окружении делает индивидуальные копии как имени переменной, так и нового ее значения</p><p class="book">Более простой альтернативой <code>setenv()</code> является <code>putenv()</code>, которая берет одну строку «<code><i>имя</i>=<i>значение</i></code>» и помещает ее в окружение:</p><p class="book"><code>if (putenv("PATH=/bin:/usr/bin:/usr/ucb") != 0) {</code></p><p class="book"><code>&nbsp;/* обработать ошибку */</code></p><p class="book"><code>}</code></p><p class="book"><code>putenv()</code> слепо заменяет любые предшествующие значения для той же переменной. А также, и это, возможно, более важно, строка, переданная <code>putenv()</code>, помещается непосредственно в окружение. Это означает, что если ваш код позже изменит эту строку (например, если это был массив, а не строковая константа), окружение также будет изменено. Это, в свою очередь, означает, что вам не следует использовать в качестве параметров для <code>putenv()</code> локальную переменную. По всем этим причинам <code>setenv()</code> является более предпочтительной функцией.</p><blockquote class="book"> <p class="book"><b>ЗАМЕЧАНИЕ</b>. GNU <code>putenv()</code> имеет дополнительную (документированную) особенность в своем поведении. Если строка аргумента является именем без следующего за ним символа <code>=</code>, именованная переменная удаляется. Программа GNU <code>env</code>, которую мы рассмотрим далее в мой главе, полагается на такое поведение.</p></blockquote><p class="book">Функция <code>unsetenv()</code> удаляет переменную из окружения:</p><p class="book"><code>unsetenv("PATH");</code></p><p class="book">Наконец, функция <code>clearenv()</code> полностью очищает окружение:</p><p class="book"><code>if (clearenv() != 0) {</code></p><p class="book"><code>&nbsp;/* обработать ошибку */</code></p><p class="book"><code>}</code></p><p class="book">Эта функция не стандартизирована POSIX, хотя она доступна в GNU/Linux и нескольких коммерческих вариантах Unix. Ее следует использовать, если приложение должно быть очень безопасным и нужно построить собственное окружение с нуля. Если <code>clearenv()</code> недоступна, в справке GNU/Linux для <i>clearenv</i>(3) рекомендуется использовать для выполнения этой задачи '<code>environ = NULL</code>'.</p><a name="t38"></a><h3 class="book"> 2.4.2. Окружение в целом: <code>environ</code><br></h3><p class="book">Правильным способом работы с окружением является использование функций, описанных в предыдущем разделе. Однако, стоит взглянуть на то, как это работает «под капотом».</p><p class="book">Внешняя переменная <code>environ</code> предоставляет доступ таким же способом, как <code>argv</code> предоставляет доступ к аргументам командной строки. Вы сами должны объявить переменную. Хотя она и стандартизирована POSIX, <code>environ</code> намеренно не объявлена ни в одном стандартном заголовочном файле (Это, кажется, прослеживается из исторической практики.) Вот объявление:</p><p class="book"><code>extern char **environ; /* Смотрите, нет заголовочного файла POSIX */</code></p><p class="book">Как и в <code>argv</code>, завершающим элементом <code>environ</code> является <code>NULL</code>. Однако, здесь нет переменной «числа строк окружения», которая соответствовала бы <code>argc</code>. Следующая простая программа распечатывает все окружение:</p><p class="book"><code>/* ch02-printenv.c --- Распечатать окружение. */</code></p><p class="book"><code>#include &lt;stdio.h&gt;</code></p><br><p class="book"><code>extern char **environ;</code></p><p class="book"><code>int main(int argc, char **argv) {</code></p><p class="book"><code>&nbsp;int i;</code></p><p class="book"><code>&nbsp;if (environ != NULL)</code></p><p class="book"><code>&nbsp; for (i = 0; environ[i] != NULL; i++)</code></p><p class="book"><code>&nbsp;&nbsp; printf("%s\n", environ[i]);</code></p><p class="book"><code>&nbsp;return 0;</code></p><p class="book"><code>}</code></p><p class="book">Хотя это и маловероятно, перед попыткой использовать <code>environ</code> эта программа проверяет, что она не равна <code>NULL</code>.</p><p class="book">Переменные хранятся в окружении в случайном порядке. Хотя некоторые оболочки Unix хранят переменные окружения в отсортированном по именам переменных виде, это формально не требуется, и многие оболочки не сортируют их.</p><p class="book">В качестве уловки реализации можно получить доступ к окружению, объявив <i>третий</i> параметр <code>main()</code>:</p><p class="book"><code>int main(int argc, char **argv, char **envp) {</code></p><p class="book"><code>&nbsp;...</code></p><p class="book"><code>}</code></p><p class="book">Затем можно использовать <code>envp</code> также, как <code>environ</code>. Хотя это иногда можно увидеть в старом коде, мы не рекомендуем такое использование; <code>environ</code> является официальным, стандартным, переносимым способом получения доступа ко всему окружению, если это вам необходимо.</p><a name="t39"></a><h3 class="book"> 2.4.3. GNU <code>env</code><br></h3><p class="book">Чтобы завершить главу, рассмотрим GNU версию команды <code>env</code>. Эта команда добавляет переменные к окружению в ходе выполнения одной команды. Она может использоваться также для очищения окружения в ходе этой команды или для удаления отдельных переменных окружения. Программа обеспечивает нас двойной функциональностью, поскольку проявляет возможности как <code>getopt_long()</code>, так и несколько других возможностей, обсуждавшихся в этом разделе. Вот как вызывается программа:</p><p class="book"><code>$ env --help</code></p><p class="book"><code>Usage: env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]</code></p><p class="book"><code>/* Устанавливает соответствующее VALUE для каждого NAME и запускает COMMAND */</code></p><p class="book"><code>-i, --ignore-environment /* запустить с пустым окружением */</code></p><p class="book"><code>-u, --unset=NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* удалить переменную из окружения */</code></p><p class="book"><code>--help&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* показать этот экран справки и выйти */</code></p><p class="book"><code>--version&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* вывести информацию о версии и выйти */</code></p><p class="book"><code>/* Простое - предполагает -1. Если не указана COMMAND, отображает</code></p><p class="book"><code>&nbsp;&nbsp; имеющееся окружение.</code></p><p class="book"><code>Об ошибках сообщайте в &lt;bug-coreutils@gnu.org&gt;. */</code></p><p class="book">Вот несколько примеров вызовов команды:</p><p class="book"><code>$ <b>env - myprog arg1</b> /* Очистить окружение, запустить программу с args */</code></p><p class="book"><code>$ <b>env - РАТН=/bin:/usr/bin myprog arg1</b> /* Очистить окружение, добавить PATH, запустить программу */</code></p><p class="book"><code>$ <b>env -u IFS PATH=/bin:/usr/bin myprog arg1</b> /* Сбросить IFS, добавить PATH, запустить программу */</code></p><p class="book">Код начинается со стандартной формулировки авторских прав GNU и разъясняющего комментария. Мы для краткости их опустили. (Формулировка авторского права обсуждается в Приложении С «Общедоступная лицензия GNU». Показанного ранее вывода <code>--help</code> достаточно для понимания того, как работает программа.) За объявленным авторским правом и комментарием следуют подключаемые заголовочные файлы и объявления. Вызов макроса '<code>N_("string")</code>' (строка 93) предназначен для использования при локализации программного обеспечения, тема, освещенная в главе 13 «Интернационализация и локализация». Пока вы можете рассматривать его, как содержащий строковую константу.</p><p class="book"><code>80&nbsp; #include &lt;config.h&gt;</code></p><p class="book"><code>81&nbsp; #include &lt;stdio.h&gt;</code></p><p class="book"><code>82&nbsp; #include &lt;getopt.h&gt;</code></p><p class="book"><code>83&nbsp; #include &lt;sys/types.h&gt;</code></p><p class="book"><code>84&nbsp; #include &lt;getopt.h&gt;</code></p><p class="book"><code>85</code></p><p class="book"><code>86&nbsp; #include "system.h"</code></p><p class="book"><code>87&nbsp; #include "error.h"</code></p><p class="book"><code>88&nbsp; #include "closeout.h"</code></p><p class="book"><code>89</code></p><p class="book"><code>90&nbsp; /* Официальное имя этой программы (напр., нет префикса 'g'). */</code></p><p class="book"><code>91&nbsp; #define PROGRAM_NAME "env"</code></p><p class="book"><code>92</code></p><p class="book"><code>93&nbsp; #define AUTHORS N_ ("Richard Mlynarik and David MacKenzie")</code></p><p class="book"><code>94</code></p><p class="book"><code>95&nbsp; int putenv();</code></p><p class="book"><code>96</code></p><p class="book"><code>97&nbsp; extern char **environ;</code></p><p class="book"><code>98</code> </p><p class="book"><code>99&nbsp; /* Имя, посредством которого эта программа была запущена. */</code></p><p class="book"><code>100 char *program_name;</code></p><p class="book"><code>101</code></p><p class="book"><code>102 static struct option const longopts[] =</code></p><p class="book"><code>103&nbsp; {</code></p><p class="book"><code>104&nbsp; {"ignore-environment", no_argument, NULL, 'i'},</code></p><p class="book"><code>105&nbsp; {"unset", required_argument, NULL, 'u'},</code></p><p class="book"><code>106&nbsp; {GETOPT_HELP_OPTION_DECL},</code></p><p class="book"><code>107&nbsp; {GETOPT_VERSION_OPTION_DECL},</code></p><p class="book"><code>108&nbsp; {NULL, 0, NULL, 0}</code></p><p class="book"><code>109 };</code></p><p class="book">GNU Coreutils содержит большое число программ, многие из которых выполняют одни и те же общие задачи (например, анализ аргументов). Для облегчения сопровождения многие типичные идиомы были определены в виде макросов. Двумя таким макросами являются <code>GETOPT_HELP_OPTION_DECL</code> и <code>GETOPT_VERSION_OPTION</code> (строки 106 и 107). Вскоре мы рассмотрим их определения. Первая функция, <code>usage()</code>, выводит информацию об использовании и завершает программу. Макрос <code>_("string")</code> (строка 115, используется также по всей программе) также предназначен для локализации, пока также считайте его содержащим строковую константу.</p><p class="book"><code>111 void</code></p><p class="book"><code>112 usage(int status)</code></p><p class="book"><code>113 {</code></p><p class="book"><code>114 &nbsp;if (status '= 0)</code></p><p class="book"><code>115&nbsp;&nbsp; fprintf(stderr, _("Try '%s --help' for more information.\n"),</code></p><p class="book"><code>116&nbsp;&nbsp;&nbsp; program_name);</code></p><p class="book"><code>117&nbsp; else</code></p><p class="book"><code>118&nbsp; {</code></p><p class="book"><code>119&nbsp;&nbsp; printf (_("\</code></p><p class="book"><code>120&nbsp;&nbsp;&nbsp; Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"),</code></p><p class="book"><code>121&nbsp;&nbsp;&nbsp; program_name);</code></p><p class="book"><code>122&nbsp;&nbsp; fputs (_("\</code></p><p class="book"><code>123&nbsp;&nbsp;&nbsp; Set each NAME to VALUE in the environment and run COMMAND. \n\</code></p><p class="book"><code>124&nbsp;&nbsp;&nbsp; \n\</code></p><p class="book"><code>125&nbsp;&nbsp;&nbsp; -i, --ignore-environment start with an empty environment\n\</code></p><p class="book"><code>126&nbsp;&nbsp;&nbsp; -u, --unset=NAME remove variable from the environment\n\</code></p><p class="book"><code>127&nbsp;&nbsp;&nbsp; "), stdout);</code></p><p class="book"><code>128&nbsp;&nbsp; fputs(HELP_OPTION_DESCRIPTION, stdout);</code></p><p class="book"><code>129&nbsp;&nbsp; fputs(VERSION_OPTION_DESCRIPTION, stdout);</code></p><p class="book"><code>130&nbsp;&nbsp; fputs(_("\</code></p><p class="book"><code>131&nbsp;&nbsp;&nbsp; \n\</code></p><p class="book"><code>132&nbsp;&nbsp;&nbsp; A mere - implies -i. If no COMMAND, print the resulting\</code></p><p class="book"><code>133&nbsp;&nbsp;&nbsp; environment.\n"), stdout);</code></p><p class="book"><code>134&nbsp;&nbsp; printf(_("\nReport bugs to &lt;%s&gt;.\n"), PACKAGE_BUGREPORT);</code></p><p class="book"><code>135&nbsp; }</code></p><p class="book"><code>136&nbsp; exit(status);</code></p><p class="book"><code>137 }</code></p><p class="book">Первая часть <code>main()</code> объявляет переменные и настраивает локализацию. Функции <code>setlocale()</code>, <code>bindtextdomain()</code> и <code>textdomain()</code> (строки 147–149) обсуждаются в главе 13 «Интернационализация и локализация». Отметим, что эта программа использует аргумент <code>main() envp</code> (строка 140). Это единственная программа Coreutils, которая так делает. Наконец, вызов <code>atexit()</code> в строке 151 (см. раздел 9.1.5.3. «Функции завершения») регистрирует библиотечную функцию Coreutils, которая очищает все выходные буферы и закрывает <code>stdout</code>, выдавая сообщение при ошибке. Следующая часть программы обрабатывает аргументы командной строки, используя <code>getopt_long()</code>.</p><p class="book"><code>139 int</code></p><p class="book"><code>140 main(register int argc, register char **argv, char **envp)</code></p><p class="book"><code>141 {</code></p><p class="book"><code>142 &nbsp;char *dummy_environ[1];</code></p><p class="book"><code>143 &nbsp;int optc;</code></p><p class="book"><code>144&nbsp; int ignore_environment = 0;</code></p><p class="book"><code>145</code></p><p class="book"><code>146&nbsp; program_name = argv[0];</code></p><p class="book"><code>147&nbsp; setlocale(LC_ALL, "");</code></p><p class="book"><code>148&nbsp; bindtextdomain(PACKAGE, LOCALEDIR);</code></p><p class="book"><code>149&nbsp; textdomain(PACKAGE);</code></p><p class="book"><code>150</code></p><p class="book"><code>151&nbsp; atexit(close_stdout);</code></p><p class="book"><code>152</code></p><p class="book"><code>153&nbsp; while ((optc = getopt_long(argc, argv, "+iu:", longopts, NULL)) != -1)</code></p><p class="book"><code>154&nbsp; {</code></p><p class="book"><code>155&nbsp;&nbsp; switch (optc)</code></p><p class="book"><code>156&nbsp;&nbsp; {</code></p><p class="book"><code>157&nbsp;&nbsp;&nbsp;case 0:</code></p><p class="book"><code>158&nbsp;&nbsp;&nbsp; break;</code></p><p class="book"><code>159&nbsp;&nbsp; case 'i':</code></p><p class="book"><code>160&nbsp;&nbsp;&nbsp; ignore_environment = 1;</code></p><p class="book"><code>161&nbsp;&nbsp;&nbsp; break;</code></p><p class="book"><code>162&nbsp;&nbsp; case 'u':</code></p><p class="book"><code>163&nbsp;&nbsp;&nbsp; break;</code></p><p class="book"><code>164&nbsp;&nbsp; case_GETOPT_HELP_CHAR;</code></p><p class="book"><code>165&nbsp;&nbsp; case_GETOPT_VERSION_CHAR(PROGRAM_NAME, AUTHORS);</code></p><p class="book"><code>166&nbsp;&nbsp; default:</code></p><p class="book"><code>167&nbsp;&nbsp;&nbsp; usage(2);</code></p><p class="book"><code>168&nbsp;&nbsp; }</code></p><p class="book"><code>169&nbsp; }</code></p><p class="book"><code>170</code> </p><p class="book"><code>171&nbsp; if (optind != argc &amp;&amp; !strcmp(argv[optind], "-"))</code></p><p class="book"><code>172&nbsp;&nbsp; ignore_environment = 1;</code></p><p class="book">Вот отрывок из файла <code>src/sys2.h</code> в дистрибутиве Coreutils с упомянутыми ранее определениями и макросом '<code>case_GETOPT_xxx</code>', использованным выше (строки 164–165):</p><p class="book"><code>/* Вынесение за скобки общей части кода, обрабатывающего --help и</code></p><p class="book"><code>&nbsp; &nbsp;--version. */</code></p><p class="book"><code>/* Эти значения перечисления никак не могут конфликтовать со значениями опций,</code></p><p class="book"><code>&nbsp; &nbsp;обычно используемыми командами, включая CHAR_MAX + 1 и т.д. Избегайте</code></p><p class="book"><code>&nbsp; &nbsp;CHAR_MIN - 1, т.к. оно может равняться -1, значение завершения опций getopt.</code></p><p class="book"><code>*/</code></p><p class="book"><code>enum {</code></p><p class="book"><code>&nbsp;GETOPT_HELP_CHAR = (CHAR_MIN — 2),</code></p><p class="book"><code>&nbsp;GETOPT_VERSION_CHAR = (CHAR_MIN - 3)</code></p><p class="book"><code>};</code></p><p class="book"><code>#define GETOPT_HELP_OPTION_DECL \</code></p><p class="book"><code>&nbsp;"help", no_argument, 0, GETOPT_HELP_CHAR</code></p><p class="book"><code>#define GETOPT_VERSION_OPTION_DECL \</code></p><p class="book"><code>&nbsp;"version", no_argument, 0, GETOPT_VERSION_CHAR</code></p><p class="book"><code>#define case_GETOPT_HELP_CHAR \</code></p><p class="book"><code>&nbsp;case GETOPT_HELP_CHAR: \</code></p><p class="book"><code>&nbsp; usage(EXIT_SUCCESS); \</code></p><p class="book"><code>&nbsp; break;</code></p><p class="book"><code>#define case_GETOPT_VERSION_CHAR(Program_name, Authors) \</code></p><p class="book"><code>&nbsp;case GETOPT_VERSION_CHAR: \</code></p><p class="book"><code>&nbsp; version_etc(stdout, Program_name, PACKAGE, VERSION, Authors); \</code></p><p class="book"><code>&nbsp; exit(EXIT_SUCCESS); \</code></p><p class="book"><code>&nbsp; break;</code></p><p class="book">Результатом этого кода является печать сообщения об использовании утилиты для <code>--help</code> и печать информации о версии для <code>--version</code>. Обе опции завершаются успешно («Успешный» и «неудачный» статусы завершения описаны в разделе 9.1.5.1 «Определение статуса завершения процесса».) Поскольку в Coreutils входят десятки утилит, имеет смысл вынести за скобки и стандартизовать как можно больше повторяющегося кода.</p><p class="book">Возвращаясь к <code>env.с</code>:</p><p class="book"><code>174 environ = dummy_environ;</code></p><p class="book"><code>175 environ[0] = NULL;</code></p><p class="book"><code>176</code></p><p class="book"><code>177 if (!ignore_environment)</code></p><p class="book"><code>178&nbsp; for (; *envp; envp++)</code></p><p class="book"><code>179&nbsp;&nbsp; putenv(*envp);</code></p><p class="book"><code>180</code></p><p class="book"><code>181 optind = 0; /* Принудительная реинициализация GNU getopt. */</code></p><p class="book"><code>182&nbsp;while ((optc = getopt_long(argc, argv, "+iu:", longopts, NULL)) != -1)</code></p><p class="book"><code>183&nbsp; if (optc == 'u')</code></p><p class="book"><code>184&nbsp;&nbsp; putenv(optarg); /* Требуется GNU putenv. */</code></p><p class="book"><code>185</code></p><p class="book"><code>186 if (optind !=argc &amp;&amp; !strcmp(argv[optind], "-")) /* Пропустить опции */</code></p><p class="book"><code>187&nbsp; ++optind;</code></p><p class="book"><code>188</code></p><p class="book"><code>189 while (optind &lt; argc &amp;&amp; strchr(argv[optind], '=')) /* Установить</code></p><p class="book"><code>&nbsp;&nbsp;&nbsp;&nbsp; переменные окружения * /</code></p><p class="book"><code>190 putenv(argv[optind++]);</code></p><p class="book"><code>191</code></p><p class="book"><code>192 /* Если программа не указана, напечатать переменные окружения и выйти. */</code></p><p class="book"><code>193 if (optind == argc)</code></p><p class="book"><code>194 {</code></p><p class="book"><code>195&nbsp; while (*environ)</code></p><p class="book"><code>196&nbsp;&nbsp; puts (*environ++);</code></p><p class="book"><code>197&nbsp; exit(EXIT_SUCCESS);</code></p><p class="book"><code>198 }</code></p><p class="book">Строки 174–179 переносят существующие переменные в новую копию окружения. В глобальную переменную <code>environ</code> помещается указатель на пустой локальный массив. Параметр <code>envp</code> поддерживает доступ к первоначальному окружению.</p><p class="book">Строки 181–184 удаляют переменные окружения, указанные в опции <code>-u</code>. Программа осуществляет это, повторно сканируя командную строку и удаляя перечисленные там имена. Удаление переменных окружения основывается на обсуждавшейся ранее особенности GNU <code>putenv()</code>: при вызове с одним лишь именем переменной (без указанного значения) <code>putenv()</code> удаляет ее из окружения.</p><p class="book">После опций в командной строке помещаются новые или замещающие переменные окружения. Строки 189–190 продолжают сканирование командной строки, отыскивая установки переменных окружения в виде '<code><i>имя</i>=<i>значение</i></code>'.</p><p class="book">По достижении строки 192, если в командной строке ничего не осталось, предполагается, что <code>env</code> печатает новое окружение и выходит из программы. Она это и делает (строки 195–197).</p><p class="book">Если остались аргументы, они представляют имя команды, которую нужно вызвать, и аргументы для передачи этой новой команде. Это делается с помощью системного вызова <code>execvp()</code> (строка 200), который <i>замещает</i> текущую программу новой. (Этот вызов обсуждается в разделе 9.1.4 «Запуск новой программы: семейство <code>exec()</code>»; пока не беспокойтесь о деталях.) Если этот вызов возвращается в текущую программу, он <i>потерпел неудачу</i>. В таком случае <code>env</code> выводит сообщение об ошибке и завершает программу.</p><p class="book"><code>200&nbsp; execvp(argv[optind], &amp;argv[optind]);</code></p><p class="book"><code>201</code></p><p class="book"><code>202&nbsp; {</code></p><p class="book"><code>203 &nbsp; int exit_status = (errno == ENOENT ? 127 : 126);</code></p><p class="book"><code>204&nbsp;&nbsp; error(0, errno, "%s", argv[optind]);</code></p><p class="book"><code>205&nbsp;&nbsp; exit(exit_status);</code></p><p class="book"><code>206&nbsp; }</code></p><p class="book"><code>207 }</code></p><p class="book">Значения кода завершения <code>126</code> и <code>127</code> (определяемые в строке 203) соответствуют стандарту POSIX. <code>127</code> означает, что программа, которую <code>execvp()</code> попыталась запустить, не существует. (<code>ENOENT</code> означает, что файл не содержит записи в каталоге.) <code>126</code> означает, что файл существует, но была какая-то другая ошибка.</p><a name="t40"></a><h3 class="book">2.5. Резюме<br> </h3> <p class="book">•&nbsp;Программы на С получают аргументы своей командной строки через параметры <code>argc</code> и <code>argv</code>. Функция <code>getopt()</code> предоставляет стандартный способ для последовательного разбора опций и их аргументов GNU версия <code>getopt()</code> предоставляет некоторые расширения, a <code>getopt_long()</code> и <code>getopt_long_only()</code> дает возможность легкого разбора длинных опций.</p> <p class="book">•&nbsp;Окружение представляет собой набор пар '<code><i>имя</i>=<i>значение</i></code>', который каждая программа наследует от своего родителя. Программы могут по прихоти своего автора использовать для изменения своего поведения переменные окружения, в дополнение к любым аргументам командной строки. Для получения значений переменных окружения, изменения их значений или удаления существуют стандартные процедуры (<code>getenv()</code>, <code>setenv()</code>, <code>putenv()</code> и <code>unsetenv()</code>). При необходимости можно получить доступ ко всему окружению через внешнюю переменную <code>environ</code> или через третий аргумент <code>char **envp</code> функции <code>main()</code>. Последний способ не рекомендуется.</p> <a name="t41"></a><h3 class="book">Упражнения<br> </h3> <p class="book">1.&nbsp;Предположим, что программа принимает опции <code>-a</code>, <code>-b</code> и <code>-с</code>, и что <code>-b</code> требует наличия аргумента. Напишите для этой программы код ручного разбора аргументов без использования <code>getopt()</code> или <code>getopt_long()</code>. Для завершения обработки опций принимается <code>--</code>. Убедитесь, что -ас работает, также, как <code>-bYANKEES</code>, <code>-b YANKEES</code> и <code>-abYANKEES</code>. Протестируйте программу.</p> <p class="book">2.&nbsp;Реализуйте <code>getopt()</code>. Для первой версии вы можете не беспокоиться насчет случая '<code>optstring[0] == ':'</code>'. Можете также игнорировать <code>opterr</code>.</p> <p class="book">3.&nbsp;Добавьте код для '<code>optstring[0] == ':'</code>' и <code>opterr</code> к своей версии <code>getopt()</code>.</p> <p class="book">4.&nbsp;Распечатайте и прочтите файлы GNU <code>getopt.h</code>, <code>getopt.с</code> и <code>getopt1.с</code>.</p> <p class="book">5.&nbsp;Напишите программу, которая объявляет как <code>environ</code>, так и <code>envp</code>, и сравните их значения.</p> <p class="book">6.&nbsp;Разбор аргументов командной строки и опций является тем колесом, которое многие люди не могут не изобретать вновь. Вы можете захотеть познакомиться с различными анализирующими аргументы пакетами, помимо <code>getopt()</code> и <code>getopt_long()</code>, такими, как:</p> <p class="book">&nbsp;•&nbsp;библиотека анализа аргументов <i>Plan 9 From Bell Labs arg(2)</i><sup><a name="anotelink31" id="anotelink31" href="#n31" title=" http://plan9.bell-labs.com/magic/man2html/2/arg — Примеч. автора.">[31]</a></sup>,</p> <p class="book">&nbsp;•&nbsp;Argp<sup><a name="anotelink32" id="anotelink32" href="#n32" title=" http://www.gnu.org/manual/glibc/html_node/Argp.html — Примеч. автора.">[32]</a></sup>,</p> <p class="book">&nbsp;•&nbsp;Argv<sup><a name="anotelink33" id="anotelink33" href="#n33" title=" http://256.com/sources/argv — Примеч. автора.">[33]</a></sup>,</p> <p class="book">&nbsp;•&nbsp;Autoopts<sup><a name="anotelink34" id="anotelink34" href="#n34" title=" http://autogen.sourceforge.net/autoopts.html — Примеч. автора.">[34]</a></sup>,</p> <p class="book">&nbsp;•&nbsp;GNU Gengetopt<sup><a name="anotelink35" id="anotelink35" href="#n35" title=" ftp://ftp.gnu.org/gnu/gengetopt/ — Примеч. автора.">[35]</a></sup>,</p> <p class="book">&nbsp;•&nbsp;Opt<sup><a name="anotelink36" id="anotelink36" href="#n36" title=" http://nis-www.lanl.gov/~jt/Software/opt/opt-3.19.tar.gz — Примеч. автора.">[36]</a></sup>,</p> <p class="book">&nbsp;•&nbsp;Popt<sup><a name="anotelink37" id="anotelink37" href="#n37" title=" http://freshmeat.net/projects/popt/?topic_id=809 — Примеч. автора.">[37]</a></sup>. См. также справочную страницу <i>popt</i>(3) системы GNU/Linux.</p> <p class="book">7.&nbsp;Дополнительный балл, почему компилятор С не может полностью игнорировать ключевое слово register? Подсказка: какие действия невозможно совершать с регистровой переменной?</p><a name="t42"></a><h3 class="book"> Глава 3<br> Управление памятью на уровне пользователя<br></h3> <p class="book">Без памяти для хранения данных программа не может выполнить никакую работу (Или, скорее, невозможно выполнить никакую <i>полезную</i> работу.) Реальные программы не могут позволить себе полагаться на буферы и массивы структур данных фиксированного размера. Они должны быть способны обрабатывать вводимые данные различных размеров, от незначительных до больших. Это, в свою очередь, ведет к использованию <i>динамически выделяемой памяти</i> — памяти, выделяемой в ходе исполнения, а не при компиляции. Вот как вводится в действие принцип GNU «никаких произвольных ограничений».</p> <p class="book">Поскольку динамически выделяемая память является основным строительным блоком для реальных программ, мы рассмотрим этот вопрос в начале, до рассмотрения всего остального. Наше обсуждение фокусируется на рассмотрении процесса и его памяти исключительно на уровне пользователя; оно не имеет ничего общего с архитектурой процессора.</p> <a name="t43"></a><h3 class="book">3.1. Адресное пространство Linux/Unix<br> </h3> <p class="book">В качестве рабочего определения мы приняли, что процесс является запушенной программой. Это означает, что операционная система загрузила исполняемый файл для этой программы в память, сделала доступными аргументы командной строки и переменные окружения и запустила ее. Процесс имеет пять выделенных для него концептуально различных областей памяти:</p> <p class="book"><i>Код</i></p> <p class="book">Часто называемая <i>сегментом текста</i> область, в которой находятся исполняемые инструкции. Linux и Unix организуют вещи таким образом, что несколько запушенных экземпляров одной программы по возможности разделяют свой код; в любое время в памяти находится лишь одна копия инструкций одной и той же программы (Это прозрачно для работающих программ.) Часть исполняемого файла, содержащая сегмент текста, называется секцией текста.</p> <p class="book"><i>Инициализированные данные</i></p> <p class="book">Статически выделенные и глобальные данные, которые инициализированы ненулевыми значениями, находятся в <i>сегменте данных</i>. У каждого процесса с одной и той же запущенной программой свой собственный сегмент данных. Часть исполняемого файла, содержащая сегмент данных, является <i>секцией данных</i>.</p> <p class="book"><i>Инициализированные нулями данные</i><sup><a name="anotelink38" id="anotelink38" href="#n38" title=" Существует также другое название для этой области данных — Неинициализированные данные — Примеч. науч. ред.">[38]</a></sup></p> <p class="book">Глобальные и статически выделенные данные, которые по умолчанию инициализированы нулями, хранятся в области процесса, который называют областью <i>BSS</i><sup><a name="anotelink39" id="anotelink39" href="#n39" title=" BSS означает 'Block Started by Symbol', мнемоника из ассемблера IBM 7094 — Примеч. автора.">[39]</a></sup>. У каждого процесса, в котором запущена одна и та же программа, своя область BSS. При запуске данные BSS помещаются в сегмент данных. В исполняемом файле они хранятся в <i>секции BSS</i>.</p> <p class="book">Формат исполняемого файла Linux/Unix таков, что пространство исполняемого файла на диске занимают лишь переменные, инициализированные ненулевыми значениями. Поэтому большой массив, объявленный как '<code>static char somebuf[2048];</code>', который автоматически заполняется нулями, не занимает 2 Кб пространства на диске. (Некоторые компиляторы имеют опции, позволяющие вам помещать инициализированные нулями данные в сегмент данных.)</p> <p class="book"><i>Куча (heap)</i></p> <p class="book"><i>Куча</i> является местом, откуда выделяется динамическая память (получаемая с помощью функции <code>malloc()</code> и подобными ей). Когда из кучи выделяется память, адресное пространство процесса растет, что вы можете заметить, отслеживая запущенный процесс с помощью команды <code>ps</code>.</p> <p class="book">Хотя память можно вернуть обратно системе и сократить адресное пространство процесса, этого почти никогда не происходит. (Мы различаем освобождение больше не использующейся динамической памяти и сокращение адресного пространства; подробнее это обсуждается далее в этой главе.)</p> <p class="book">Для кучи характерен «рост вверх». Это означает, что последовательные элементы, добавляемые к куче, добавляются по адресам, численно превосходящим предыдущие. Куча обычно начинается сразу после области BSS сегмента данных.</p> <p class="book"><i>Стек</i></p> <p class="book"><i>Сегмент стека</i> — это область, в которой выделяются локальные переменные. Локальными являются все переменные, объявленные внутри левой открывающей фигурной скобки тела функции (или другой левой фигурной скобки) и не имеющие ключевого слова <code>static</code>.</p> <p class="book">В большинстве архитектур параметры функций также помещаются в стек наряду с «невидимой» учетной информацией, генерируемой компилятором, такой, как возвращаемое функцией значение и адрес возврата для перехода из функции к месту, откуда произошел вызов. (В некоторых архитектурах для этого используются регистры.) Именно использование стека для параметров функций и возвращаемых ими значений делает удобным написание <i>рекурсивных</i> функций (тех, которые вызывают сами себя) Переменные, хранящиеся в стеке, «исчезают», когда функция, их содержащая, возвращается, пространство стека используется повторно для последующих вызовов функций. В большинстве современных архитектур стек «растет вниз», это означает, что элементы, находящиеся глубже в цепи вызова, находятся по численно меньшим адресам. В работающей программе области инициализированных данных, BSS и кучи обычно размещаются в единой протяженной области: сегменте данных. Сегменты стека и кода отделены от сегмента данных и друг от друга. Это показано на рис. 3.1.</p> <center><img border="0" style="spacing 9px;" src="/i/74/334674/img_3.png"></center> <p class="book"><b>Рис. 3.1</b>. Адресное пространство Linux/Unix</p> <p class="book">Хотя перекрывание стека и кучи теоретически возможно, операционная система предотвращает этот случай, и любая программа, пытающаяся это сделать, напрашивается на неприятности. Это особенно верно для современных систем, в которых адресные пространства большие и интервал между верхушкой стека и концом кучи значителен. Различные области памяти могут иметь различную установленную на память аппаратную защиту. Например, сегмент текста может быть помечен «только для исполнения», тогда как у сегментов данных и стека разрешение на исполнение может отсутствовать. Такая практика может предотвратить различные виды атак на безопасность. Подробности, конечно, специфичны для оборудования и операционной системы, и они могут со временем меняться. Стоит заметить, что стандартные как С, так и C++ позволяют размещать элементы с атрибутом <code>const</code> в памяти только для чтения. Сводка взаимоотношений различных сегментов приведена в табл. 3.1.</p> <br> <p class="book"><b>Таблица 3.1</b>. Сегменты исполняемой программы и их размещение</p> <table><tbody><tr align="left"> <th align="left" valign="top">Память программы</th> <th align="left" valign="top">Сегмент адресного пространства</th> <th align="left" valign="top">Секция исполняемого файла</th></tr><tr align="left"> <td align="left" valign="top">Код</td> <td align="left" valign="top">Text</td> <td align="left" valign="top">Text</td></tr><tr align="left"> <td align="left" valign="top">Инициализированные данные</td> <td align="left" valign="top">Data</td> <td align="left" valign="top">Data</td></tr><tr align="left"> <td align="left" valign="top">BSS</td> <td align="left" valign="top">Data</td> <td align="left" valign="top">BSS</td></tr><tr align="left"> <td align="left" valign="top">Куча</td> <td align="left" valign="top">Data</td> <td align="left" valign="top"></td></tr><tr align="left"> <td align="left" valign="top">Стек</td> <td align="left" valign="top">Stack</td> <td align="left" valign="top"></td></tr> </tbody></table> <p class="book">Программа <code>size</code> распечатывает размеры в байтах каждой из секций text, data и BSS вместе с общим размером в десятичном и шестнадцатеричном виде. (Программа <code>ch03-memaddr.с</code> показана далее в этой главе; см. раздел 3.2.5 «Исследование адресного пространства».)</p> <p class="book"><code>$ <b>cc -o ch03-memaddr.с -о ch03-memaddr</b> /* Компилировать программу */</code></p> <p class="book"><code>$ <b>ls -l ch03-memaddr</b> /* Показать общий размер */</code></p> <p class="book"><code>-rwxr-xr-x 1 arnold devel 12320 Nov 24 16:45 ch03-memaddr</code></p> <p class="book"><code>$ <b>size ch03-memaddr</b> /* Показать размеры компонентов */</code></p> <p class="book"><code>text data bss dec&nbsp; hex filename</code></p> <p class="book"><code>1458 276&nbsp; 8&nbsp;&nbsp; 1742 6ce ch03-memaddr</code></p> <br> <p class="book"><code>$ <b>strip ch03-memaddr</b> /* Удалить символы */</code></p> <p class="book"><code>$ <b>ls -l ch03-memaddr</b> /* Снова показать общий размер */</code></p> <p class="book"><code>-rwxr-xr-x 1 arnold devel 3480 Nov 24 16:45 ch03-memaddr</code></p> <p class="book"><code>$ <b>size ch03-memaddr</b> /* Размеры компонентов не изменились */</code></p> <p class="book"><code>text data bss dec&nbsp; hex filename</code></p> <p class="book"><code>1458 276&nbsp; 8&nbsp;&nbsp; 1742 6ce ch03-memaddr</code></p> <p class="book">Общий размер загруженного в память из файла в 12&nbsp;320 байтов всего лишь 1742&nbsp;байта. Большую часть этого места занимают <i>символы (symbols)</i>, список имен переменных и функций программы. (Символы не загружаются в память при запуске программы.) Программа <code>strip</code> удаляет символы из объектного файла. Для большой программы это может сохранить значительное дисковое пространство ценой невозможности отладки дампа ядра<sup><a name="anotelink40" id="anotelink40" href="#n40" title=" Дамп ядра (core dump) является образом запущенного процесса в памяти, который создаётся при неожиданном завершении процесса. Позже этот дамп может быть использован для отладки Unix-системы, называют это файл core, а системы GNU/Linux&nbsp;— core.pid, где pid — ID потерпевшего крушения процесса — Примеч. автора.">[40]</a></sup>, если таковой появится (На современных системах об этом не стоит беспокоиться, не используйте <code>strip</code>.) Даже после удаления символов файл все еще больше, чем загруженный в память образ, поскольку формат объектного файла содержат дополнительные данные о программе, такие, как использованные разделяемые библиотеки, если они есть.<sup><a name="anotelink41" id="anotelink41" href="#n41" title=" Описание здесь намеренно упрощено. Запущенные программы занимают значительно больше места, чем указывает программа size, поскольку разделяемые библиотеки включены в адресное пространство. Также сегмент данных будет расти по мере выделения программной памяти — Примеч. автора.">[41]</a></sup></p> <p class="book">Наконец, упомянем <i>потоки (threads)</i>, которые представляют несколько цепочек исполнения в рамках <i>единственного</i> адресного пространства. Обычно у каждого потока имеется свой собственный стек, а также способ получения <i>локальных данных потока</i>, т.е. динамически выделяемых данных для персонального использования этим потоком. Мы больше не будем рассматривать в данной книге потоки, поскольку это является продвинутой темой.</p> <a name="t44"></a><h3 class="book">3.2. Выделение памяти<br> </h3> <p class="book">Четыре библиотечные функции образуют основу управления динамической памятью С Мы опишем сначала их, затем последуют описания двух системных вызовов, поверх которых построены эти библиотечные функции. Библиотечные функции С, в свою очередь, обычно используются для реализации других выделяющих память библиотечных функций и операторов C++ <code>new</code> и <code>delete</code>.</p><p class="book">Наконец, мы обсудим функцию, которую часто используют, но которую мы не рекомендуем использовать.</p><a name="t45"></a><h3 class="book"> 3.2.1. Библиотечные вызовы: <code>malloc()</code>, <code>calloc()</code>, <code>realloc()</code>, <code>free()</code><br></h3> <p class="book">Динамическую память выделяют с помощью функций <code>malloc()</code> или <code>calloc()</code>. Эти функции возвращают указатели на выделенную память. Когда у вас есть блок памяти определенного первоначального размера, вы можете изменить его размер с помощью функции <code>realloc()</code>. Динамическая память освобождается функцией <code>free()</code>.</p> <p class="book">Отладка использования динамической памяти сама по себе является важной темой. Инструменты для этой цели мы обсудим в разделе 15.5.2 «Отладчики выделения памяти».</p> <a name="t46"></a><h3 class="book">3.2.1.1. Исследование подробностей на языке С<br> </h3> <p class="book">Вот объявления функций из темы справки GNU/Linux <i>malloc</i>(3):</p> <p class="book"><code>#include &lt;stdlib.h&gt; /* ISO С */</code></p> <br> <p class="book"><code>void *calloc(size_t nmemb, size_t size);</code></p> <p class="book"><code>&nbsp;/* Выделить и инициализировать нулями */</code></p> <p class="book"><code>void *malloc(size_t size);</code></p> <p class="book"><code>&nbsp;/* Выделить без инициализации */</code></p> <p class="book"><code>void free(void *ptr);</code></p> <p class="book"><code>&nbsp;/* Освободить память */</code></p> <p class="book"><code>void *realloc(void *ptr, size_t size);</code></p> <p class="book"><code>&nbsp;/* Изменить размер выделенной памяти */</code></p> <p class="book">Функции выделения памяти возвращают тип <code>void*</code>. Это бестиповый или общий указатель, все, что с ним можно делать — это привести его к другому типу и назначить типизированному указателю. Примеры впереди.</p> <p class="book">Тип <code>size_t</code> является беззнаковым целым типом, который представляет размер памяти. Он используется для динамического выделения памяти, и далее в книге мы увидим множество примеров его использования. На большинстве современных систем <code>size</code>_t является <code>unsigned long</code>, но лучше явно использовать <code>size_t</code> вместо простого целого типа <code>unsigned</code>.</p> <p class="book">Тип <code>ptrdiff_t</code> используется для вычисления адреса в арифметике указателей, как в случае вычисления указателя в массиве:</p> <p class="book"><code>#define MAXBUF ...</code></p> <p class="book"><code>char *p;</code></p> <p class="book"><code>char buf[MAXBUF];</code></p> <p class="book"><code>ptrdiff_t where;</code></p> <br> <p class="book"><code>p = buf;</code></p> <p class="book"><code>while (/* некоторое условие */) {</code></p> <p class="book"><code>&nbsp;...</code></p> <p class="book"><code>&nbsp;p += something;</code></p> <p class="book"><code>&nbsp;...</code></p> <p class="book"><code>&nbsp;where = p - buf; /* какой у нас индекс? */</code></p> <p class="book"><code>}</code></p> <p class="book">Заголовочный файл <code>&lt;stdlib.h&gt;</code> объявляет множество стандартных библиотечных функций С и типов (таких, как <code>size_t</code>), он определяет также константу препроцессора <code>NULL</code>, которая представляет «нуль» или недействительный указатель. (Это нулевое значение, такое, как 0 или '<code>((void*)0)</code>'. Явное использование 0 относится к стилю С++; в С, однако, <code>NULL</code> является предпочтительным, мы находим его гораздо более читабельным для кода С.)</p> <a name="t47"></a><h3 class="book">3.2.1.2. Начальное выделение памяти: <code>malloc()</code><br> </h3> <p class="book">Сначала память выделяется с помощью <code>malloc()</code>. Передаваемое функции значение является общим числом затребованных байтов. Возвращаемое значение является указателем на вновь выделенную область памяти или <code>NULL</code>, если память выделить невозможно. В последнем случае для обозначения ошибки будет установлен <code>errno</code>. (errno является специальной переменной, которую системные вызовы и библиотечные функции устанавливают для указания произошедшей ошибки. Она описывается в разделе&nbsp;4.3 «Определение ошибок».) Например, предположим, что мы хотим выделить переменное число некоторых структур. Код выглядит примерно так:</p> <p class="book"><code>struct coord { /* 3D координаты */</code></p> <p class="book"><code>&nbsp;int x, y, z;</code></p> <p class="book"><code>} *coordinates;</code></p> <p class="book"><code>unsigned int count; /* сколько нам нужно */</code></p> <p class="book"><code>size_t amount; /* общий размер памяти */</code></p> <p class="book"><code>/* ... как-нибудь определить нужное число... */</code></p> <p class="book"><code>amount = count * sizeof(struct coord); /* сколько байт выделить */</code></p> <p class="book"><code>coordinates = (struct coord*)malloc(amount); /* выделить память */</code></p> <p class="book"><code>if (coordinates == NULL) {</code></p> <p class="book"><code>&nbsp;/* сообщить об ошибке, восстановить или прервать */</code></p> <p class="book"><code>}</code></p> <p class="book"><code>/* ... использовать координаты... */</code></p> <p class="book">Представленные здесь шаги являются стереотипными. Порядок следующий:</p> <p class="book">1.&nbsp;Объявить указатель соответствующего типа для выделенной памяти.</p> <p class="book">2.&nbsp;Вычислить размер выделяемой памяти в байтах. Для этого нужно умножить число нужных объектов на размер каждого из них. Последний получается с помощью оператора С <code>sizeof</code>, который для этой цели и существует (наряду с другими). Таким образом, хотя размер определенной структуры среди различных компиляторов и архитектур может различаться, <code>sizeof</code> всегда возвращает верное значение, а исходный код остается правильным и переносимым.</p> <p class="book">При выделении массивов для строк символов или других данных типа <code>char</code> нет необходимости умножения на <code>sizeof(char)</code>, поскольку последнее по определению всегда равно 1. Но в любом случае это не повредит.</p> <p class="book">3.&nbsp;Выделить память с помощью <code>malloc()</code>, присвоив возвращаемое функцией значение переменной указателя. Хорошей практикой является приведение возвращаемого <code>malloc()</code> значения к типу переменной, которой это значение присваивается. В С этого не требуется (хотя компилятор может выдать предупреждение). Мы настоятельно рекомендуем всегда приводить возвращаемое значение.</p> <p class="book">Обратите внимание, что на C++ присвоение знамения указателя одного типа указателю другого типа требует приведения типов, какой бы ни был контекст. Для управления динамической памятью программы C++ должны использовать <code>new</code> и <code>delete</code>, а не <code>malloc()</code> и <code>free()</code>, чтобы избежать проблем с типами.</p> <p class="book">4.&nbsp;Проверить возвращенное значение. <i>Никогда</i> не предполагайте, что выделение памяти было успешным. Если выделение памяти завершилось неудачей, <code>malloc()</code> возвращает <code>NULL</code>. Если вы используете значение без проверки, ваша программа может быть немедленно завершена из-за <i>нарушения сегментации (segmentation violation)</i>, которое является попыткой использования памяти за пределами своего адресного пространства.</p> <p class="book">Если вы проверите возвращенное значение, вы можете по крайней мере выдать диагностическое сообщение и корректно завершить программу. Или можете попытаться использовать какой-нибудь другой способ восстановления.</p> <p class="book">Выделив блок памяти и установив в <code>coordinates</code> указатель на него, мы можем затем интерпретировать <code>coordinates</code> как массив, хотя он в действительности указатель:</p> <p class="book"><code>int cur_x, cur_y, cur_z;</code></p> <p class="book"><code>size_t an_index;</code></p> <p class="book"><code>an_index = something;</code></p> <p class="book"><code>cur_x = coordinates[an_index].x;</code></p> <p class="book"><code>cur_y = coordinates[an_index].y;</code></p> <p class="book"><code>cur_z = coordinates[an_index].z;</code></p> <p class="book">Компилятор создает корректный код для индексирования через указатель при получении доступа к членам структуры <code>coordinates[an_index]</code>.</p> <blockquote class="book"><p class="book"><b>ЗАМЕЧАНИЕ</b>. Блок памяти, возвращенный <code>malloc()</code>, не инициализирован. Он может содержать любой случайный мусор. Необходимо сразу же инициализировать память нужными значениями или хотя бы нулями. В последнем случае используйте функцию <code>memset()</code> (которая обсуждается в разделе 12.2 «Низкоуровневая память, функции <code>memXXX()</code>):</p><p class="book"><code>memset(coordinates, '\0', amount);</code></p><p class="book">Другой возможностью является использование <code>calloc()</code>, которая вскоре будет описана.</p> </blockquote> <p class="book">Джефф Колье (Geoff Collyer) рекомендует следующую методику для выделения памяти:</p> <p class="book"><code>some_type *pointer;</code></p> <p class="book"><code>pointer = malloc(count * sizeof(*pointer));</code></p> <p class="book">Этот подход гарантирует, что <code>malloc()</code> выделит правильное количество памяти без необходимости смотреть объявление pointer. Если тип <code>pointer</code> впоследствии изменится, оператор <code>sizeof</code> автоматически гарантирует, что выделяемое число байтов остается правильным. (Методика Джеффа опускает приведение типов, которое мы только что обсуждали. Наличие там приведения типов также гарантирует диагностику, если тип <code>pointer</code> изменится, а вызов <code>malloc()</code> не будет обновлен.)</p> <a name="t48"></a><h3 class="book">3.2.1.3. Освобождение памяти: <code>free()</code><br> </h3> <p class="book">Когда вы завершили использование памяти, «верните ее обратно», используя функцию <code>free()</code>. Единственный аргумент является указателем, предварительно полученным с использованием другой функции выделения. Можно (хотя это бесполезно) передать функции <code>free()</code> пустой указатель:</p> <p class="book"><code>free(coordinates);</code></p> <p class="book"><code>coordinates = NULL; /* не требуется, но хорошая мысль */</code></p> <p class="book">После вызова f<code>ree(coordinates)</code> доступ к памяти, на которую указывает <code>coordinates</code>, <i>запрещен</i>. Она теперь «принадлежит» процедурам выделения, и они могут поступать с ней как сочтут нужным. Они могут изменить содержимое памяти или даже удалить ее из адресного пространства процесса! Таким образом, есть несколько типичных ошибок, которых нужно остерегаться при использовании <code>free()</code>:</p> <p class="book"><i>Доступ к освобожденной памяти</i></p> <p class="book">Если она не была освобождена, переменная <code>coordinates</code> продолжает указывать на блок памяти, который больше не принадлежит приложению. Это называется <i>зависшим указателем (dangling pointer)</i>. На многих системах вы можете уйти от наказания, продолжая использовать эту память, по крайней мере до следующего выделения или освобождения памяти. На других системах, однако, такой доступ не будет работать. В общем, доступ к освобожденной памяти является плохой мыслью: это непереносимо и ненадежно, и <i>GNU Coding Standards</i> отвергает его. По этой причине неплохо сразу же установить в указателе программы значение <code>NULL</code>. Если затем вы случайно попытаетесь получить доступ к освобожденной памяти, программа немедленно завершится с ошибкой нарушения сегментации (надеемся, до того, как вы успели вашу программу выпустить в свет).</p> <p class="book"><i>Освобождение одного и того же указателя дважды</i></p> <p class="book">Это создает «неопределенное поведение». После передачи блока памяти обратно выделяющим процедурам они могут объединить освобожденный блок с другой свободной памятью, которая есть в их распоряжении. Освобождение чего-то уже освобожденного ведет к неразберихе и в лучшем случае к крушению; известно, что так называемые двойные освобождения приводили к проблемам безопасности.</p> <p class="book"><i>Передача указателя, полученного не от функций</i> <code><i>malloc()</i></code><i>,</i> <code><i>calloc()</i></code> <i>или</i> <code><i>realloc()</i></code></p> <p class="book">Это кажется очевидным, но тем не менее важно. Плоха даже передача указателя на адрес где-то в середине динамически выделенной памяти:</p> <p class="book"><code>free(coordinates + 10);</code></p> <p class="book"><code>/* Освободить все кроме первых 10 элементов */</code></p> <p class="book">Этот вызов не будет работать и, возможно, приведет к пагубным последствиям, таким как крушение. (Это происходит потому, что во многих реализациях <code>malloc()</code> «учетная» информация хранится перед возвращенными данными. Когда <code>free()</code> пытается использовать эту информацию, она обнаружит там недействительные данные. В других реализациях, где учетная информация хранится в конце выделенного блока; возникают те же проблемы.)</p> <p class="book"><i>Выход за пределы буфера</i></p> <p class="book">Доступ к памяти за пределами выделенного блока также ведет к неопределенному поведению, опять из-за того, что она может содержать учетную информацию или, возможно, вообще не принадлежать адресному пространству процесса. Запись в такой участок памяти гораздо хуже, поскольку это может уничтожить учетные данные.</p> <p class="book"><i>Отказ в освобождении памяти</i></p> <p class="book">Любая динамическая память, которая больше не нужна, должна быть освобождена. В частности, необходимо тщательно управлять памятью и освобождать ее, когда она выделяется внутри циклов или рекурсивных или глубоко вложенных вызовов функций. Отказ от этого ведет к <i>утечкам памяти</i>, при которых память процесса может неограниченно расти; в конце концов, процесс завершается из-за нехватки памяти. Эта ситуация может быть особенно разрушительной, если память выделяется для ввода записи или как-то еще связана с вводом: утечка памяти будет незаметна при использовании незначительных объемов ввода, но внезапно станет очевидной (и приведет в замешательство) при больших. Эта ошибка еще хуже для систем, которые должны работать непрерывно, как в системах телефонных коммутаторов. Утечка памяти, вызывающая крушение такой системы, может привести к значительным денежным или другим потерям.</p> <p class="book">Даже если программа никогда не завершается из-за недостатка памяти, постоянно увеличивающиеся программы теряют производительность, поскольку операционная система должна сохранять использующиеся данные в физической памяти. В худшем случае, это может привести к поведению, известному как <i>пробуксовка (thrashing)</i>, при которой операционная система так занята перекачкой содержимого адресного пространства в и из физической памяти, что реальная работа не делается.</p> <p class="book">Хотя <code>free()</code> может вернуть освобожденную память системе и сократить адресное пространство процесса, это почти никогда не делается. Вместо этого освобожденная память готова для нового выделения при следующем вызове <code>malloc()</code>, <code>calloc()</code> или <code>realloc()</code>.</p> <p class="book">При условии, что освобожденная память продолжает оставаться в адресном пространстве процесса, стоит обнулить ее перед освобождением. Например, такой способ может оказаться предпочтительным для программ с повышенными требованиями к безопасности.</p> <p class="book">Обсуждение ряда полезных инструментов для отладки динамической памяти см в разделе 15.5.2 «Отладчики выделения памяти».</p> <a name="t49"></a><h3 class="book">3.2.1.4. Изменение размера: <code>realloc()</code><br> </h3> <p class="book">Динамическая память имеет существенное преимущество перед статически объявленными массивами, поскольку это позволяет использовать столько памяти, сколько нужно, и не больше. Не нужно объявлять глобальный, статический или локальный массив фиксированного размера и надеяться, что он: (а) достаточно большой и (б) не слишком большой. Вместо этого можно выделить ровно столько, сколько нужно, не больше и не меньше.</p> <p class="book">Вдобавок, можно изменять размер динамически выделенной области памяти. Хотя можно сократить размер блока памяти, обычно его увеличивают. Изменение размера осуществляется с помощью <code>realloc()</code>. Продолжая пример с <code>coordinates</code>, типичный код выглядит следующим образом:</p> <p class="book"><code>int new_count;</code></p> <p class="book"><code>size_t new_amount;</code></p> <p class="book"><code>struct coord *newcoords; /* установить, например: */</code></p> <p class="book"><code>new_count = count * 2; /* удвоить размер памяти */</code></p> <p class="book"><code>new_amount = new_count * sizeof(struct coord);</code></p> <p class="book"><code>newcoords =</code></p> <p class="book"><code>&nbsp;(struct coord*)realloc(coordinates, new_amount);</code></p> <p class="book"><code>if (newcoords == NULL) {</code></p> <p class="book"><code>&nbsp;/* сообщить об ошибке, восстановить или прервать */</code></p> <p class="book"><code>}</code></p> <p class="book"><code>coordinates = newcoords;</code></p> <p class="book"><code>/* продолжить использование coordinates ... */</code></p> <p class="book">Как и в случае с <code>malloc()</code>, шаги стереотипны по природе и сходны по идее.</p> <p class="book">1.&nbsp;Вычислить новый выделяемый размер в байтах.</p> <p class="book">2.&nbsp;Вызвать <code>realloc()</code> с оригинальным указателем, полученным от <code>malloc()</code> (или от <code>calloc()</code> или предыдущего вызова <code>realloc()</code>) и с новым размером.</p> <p class="book">3.&nbsp;Привести тип и присвоить возвращенное <code>realloc()</code> значение. Подробнее обсудим дальше.</p> <p class="book">4.&nbsp;Как и для <code>malloc()</code>, <i>проверить</i> возвращенное значение, чтобы убедиться, что оно не равно NULL. Вызов любой функции выделения памяти может завершиться неудачей.</p> <p class="book">При увеличении размера блока памяти <code>realloc()</code> часто выделяет новый блок нужного размера, копирует данные из старого блока в новый и возвращает указатель уже на новый блок. При сокращении размера блока данных <code>realloc()</code> часто обновляет внутреннюю учетную информацию и возвращает тот же указатель. Это избавляет от необходимости копировать первоначальные данные. Однако, если это случится, <i>не думайте, что можно использовать память за пределами нового размера</i>!</p> <p class="book">В любом случае вы можете предположить, что если <code>realloc()</code> не возвращает <code>NULL</code>, старые данные были скопированы для вас в новый участок памяти. Более того, старый указатель больше недействителен, как если бы вы вызвали <code>free()</code> с ним, и использовать его больше не следует. Это верно для всех указателей на этот блок данных, а не только для того, который использовался при вызове <code>free()</code>.</p> <p class="book">Возможно, вы заметили, что в нашем примере для указания на измененный блок памяти использовалась отдельная переменная. Можно было бы (хотя это плохая идея) использовать ту же самую переменную, как здесь:</p> <p class="book"><code>coordinates = realloc(coordinates, new_amount);</code></p> <p class="book">Это плохо по следующей причине. Когда <code>realloc()</code> возвращает <code>NULL</code>, первоначальный указатель все еще действителен; можно безопасно продолжить использовать эту память. Но если вы повторно используете ту же самую переменную и <code>realloc()</code> возвращает <code>NULL</code>, вы <i>теряете</i> указатель на первоначальную память. Эту память больше нельзя использовать. Что еще важнее, эту память невозможно освободить! Это создает утечку памяти, которую нужно избежать.</p> <p class="book">Для версии <code>realloc()</code> в стандартном С есть некоторые особые случаи: когда аргумент <code>ptr</code> равен <code>NULL</code>, <code>realloc()</code> действует подобно <code>malloc()</code> и выделяет свежий блок памяти. Когда аргумент <code>size</code> равен 0, <code>realloc()</code> действует подобно <code>free()</code> и <i>освобождает</i> память, на которую указывает <code>ptr</code>. Поскольку (а) это может сбивать с толку и (б) более старые системы не реализуют эту возможность, мы рекомендуем использовать <code>malloc()</code>, когда вы имеете в виду <code>malloc()</code>, и <code>free()</code>, когда вы имеете в виду <code>free()</code>.</p> <p class="book">Вот другой довольно тонкий момент<sup><a name="anotelink42" id="anotelink42" href="#n42" title=" Он получен от реальной практики работы с gawk — Примеч. автора.">[42]</a></sup>. Рассмотрим процедуру, которая содержит статический указатель на динамически выделяемые данные, которые время от времени должны расти. Процедура может содержать также автоматические (т.е. локальные) указатели на эти данные. (Для краткости, мы опустим проверки ошибок. В коде продукта не делайте этого.) Например:</p> <p class="book"><code>void manage_table(void) {</code></p> <p class="book"><code>&nbsp;static struct table *table;</code></p> <p class="book"><code>&nbsp;struct table *cur, *p;</code></p> <p class="book"><code>&nbsp;int i;</code></p> <p class="book"><code>&nbsp;size_t count;</code></p> <p class="book"><code>&nbsp;...</code></p> <p class="book"><code>&nbsp;table =</code></p> <p class="book"><code>&nbsp; (struct table*)malloc(count * sizeof(struct table));</code></p> <p class="book"><code>&nbsp;/* заполнить таблицу */</code></p> <p class="book"><code>&nbsp;cur = &amp;table[i]; /* указатель на 1-й элемент */</code></p> <p class="book"><code>&nbsp;...</code></p> <p class="book"><code>&nbsp;cur-&gt;i = j; /* использование указателя */</code></p> <p class="book"><code>&nbsp;...</code></p> <p class="book"><code>&nbsp;if (/* некоторое условие */) {</code></p> <p class="book"><code>&nbsp; /* нужно увеличить таблицу */</code></p> <p class="book"><code>&nbsp; count += count/2;</code></p> <p class="book"><code>&nbsp; p =</code></p> <p class="book"><code>&nbsp; (struct table*)realloc(table, count * sizeof(struct table));</code></p> <p class="book"><code>&nbsp; table = p;</code></p> <p class="book"><code>&nbsp;}</code></p> <p class="book"><code>&nbsp;cur-&gt;i = j; /* ПРОБЛЕМА 1: обновление элемента таблицы */</code></p> <p class="book"><code>&nbsp;other_routine(); /* ПРОБЛЕМА 2: см. текст */</code></p> <p class="book"><code>&nbsp;cur-&gt;j = k; /* ПРОБЛЕМА 2: см. текст */</code></p> <p class="book"><code>&nbsp;...</code></p> <p class="book"><code>}</code></p> <p class="book">Это выглядит просто; <code>manage_table()</code> размешает данные, использует их, изменяет размер и т.д. Но есть кое-какие проблемы, которые не выходят за рамки страницы (или экрана), когда вы смотрите на этот код.</p> <p class="book">В строке, помеченной '<code>ПРОБЛЕМА 1</code>', указатель cur используется для обновления элемента таблицы. Однако, <code>cur</code> был инициализирован начальным значением <code>table</code>. Если некоторое условие верно и <code>realloc()</code> вернула другой блок памяти, <code>cur</code> теперь указывает на первоначальный, освобожденный участок памяти! Каждый раз, когда <code>table</code> меняется, нужно обновить также все указатели на этот участок памяти. Здесь после вызова <code>realloc()</code> и переназначения <code>table</code> недостает строки '<code>cur = &amp;table[i];</code>'.</p> <p class="book">Две строки, помеченные '<code>ПРОБЛЕМА 2</code>', еще более тонкие. В частности, предположим, что <code>other_routine()</code> делает рекурсивный вызов <code>manage_table()</code>. Переменная <code>table</code> снова может быть изменена совершенно незаметно! После возвращения из <code>other_routine()</code> значение cur может снова стать недействительным.</p> <p class="book">Можно подумать (что мы вначале и сделали), что единственным решением является знать это и добавить после вызова функции переназначение <code>cur</code> с соответствующим комментарием. Однако, Брайан Керниган (Brian Kernighan) любезно нас поправил. Если мы используем индексирование, проблема поддержки указателя даже не возникает:</p> <p class="book"><code>table =</code></p> <p class="book"><code>&nbsp;(struct table*)malloc(count * sizeof(struct table));</code></p> <p class="book"><code>...</code></p> <p class="book"><code>/* заполнить таблицу */</code></p> <p class="book"><code>...</code></p> <p class="book"><code>table[i].i = j; /* Обновить член i-го элемента */</code></p> <p class="book"><code>...</code></p> <p class="book"><code>if (/* некоторое условие */) {</code></p> <p class="book"><code>&nbsp;/* нужно увеличить таблицу */</code></p> <p class="book"><code>&nbsp;count += count/2;</code></p> <p class="book"><code>&nbsp;p =</code></p> <p class="book"><code>&nbsp; (struct table*)realloc(table, count * sizeof(struct table));</code></p> <p class="book"><code>&nbsp;table = p;</code></p> <p class="book"><code>}</code></p> <p class="book"><code>table[i].i = j; /* ПРОБЛЕМА 1 устраняется */</code></p> <p class="book"><code>other_routine();</code></p> <p class="book"><code>/* Рекурсивный вызов, модифицирует таблицу */</code></p> <p class="book"><code>table[i].j = k; /* ПРОБЛЕМА 2 также устраняется */</code></p> <p class="book">Использование индексирования не решает проблему, если вы используете <i>глобальную</i> копию первоначального указателя на выделенные данные; в этом случае, вам все равно нужно побеспокоиться об обновлении своих глобальных структур после вызова <code>realloc()</code>.</p> <blockquote class="book"><p class="book"><b>ЗАМЕЧАНИЕ</b>. Как и в случае с <code>malloc()</code>, когда вы увеличиваете размер памяти, вновь выделенная после <code>realloc()</code> память не инициализируется нулями. Вы сами при необходимости должны очистить память с помощью <code>memset()</code>, поскольку <code>realloc()</code> лишь выделяет новую память и больше ничего не делает.</p> </blockquote> <a name="t50"></a><h3 class="book">3.2.1.5. Выделение с инициализацией нулями: <code>calloc()</code><br> </h3> <p class="book">Функция <code>calloc()</code> является простой оболочкой вокруг <code>malloc()</code>. Главным ее преимуществом является то, что она обнуляет динамически выделенную память. Она также вычисляет за вас размер памяти, принимая в качестве параметра число элементов и размер каждого элемента:</p> <p class="book"><code>coordinates = (struct coord*)calloc(count, sizeof(struct coord));</code></p> <p class="book">По крайней мере идейно, код <code>calloc()</code> довольно простой. Вот одна из возможных реализаций:</p> <p class="book"><code>void *calloc(size_t nmemb, size_t size) {</code></p> <p class="book"><code>&nbsp;void *p;</code></p> <p class="book"><code>&nbsp;size_t total;</code></p> <p class="book"><code>&nbsp;total = nmemb * size;&nbsp;&nbsp; /* Вычислить размер */</code></p> <p class="book"><code>&nbsp;p = malloc(total);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Выделить память */</code></p> <p class="book"><code>&nbsp;if (p != NULL)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Если это сработало - */</code></p> <p class="book"><code>&nbsp;memset(p, '\0', total); /* Заполнить ее нулями */</code></p> <p class="book"><code>&nbsp;return p; /* Возвращаемое значение NULL или указатель */</code></p> <p class="book"><code>}</code></p> <p class="book">Многие опытные программисты предпочитают использовать <code>calloc()</code>, поскольку в этом случае никогда не возникает вопросов по поводу вновь выделенной памяти.</p> <p class="book">Если вы знаете, что вам понадобится инициализированная нулями память, следует также использовать <code>calloc()</code>, поскольку возможно, что память, возвращенная <code>malloc()</code>, уже заполнена нулями. Хотя вы, программист, не можете этого знать, <code>calloc()</code> может это знать и избежать лишнего вызова <code>memset()</code>.</p> <a name="t51"></a><h3 class="book">3.2.1.6. Подведение итогов из GNU Coding Standards<br> </h3> <p class="book">Чтобы подвести итоги, процитируем, что говорит об использовании процедур выделения памяти <i>GNU Coding Standards</i>:</p> <blockquote class="book"><p class="book">Проверяйте каждый вызов <code>malloc</code> или <code>realloc</code> на предмет возвращенного нуля. Проверяйте <code>realloc</code> даже в том случае, если вы уменьшаете размер блока; в системе, которая округляет размеры блока до степени двойки, <code>realloc</code> может получить другой блок, если вы запрашиваете меньше памяти.</p><p class="book">В Unix <code>realloc</code> может разрушить блок памяти, если она возвращает ноль. GNU <code>realloc</code> не содержит подобной ошибки: если она завершается неудачей, исходный блок остается без изменений. Считайте, что ошибка устранена. Если вы хотите запустить свою программу на Unix и хотите избежать потерь в этом случае, вы можете использовать GNU <code>malloc</code>.</p><p class="book">Вы должны считать, что <code>free</code> изменяет содержимое освобожденного блока. Все, что вы хотите получить из блока, вы должны получать до вызова <code>free</code>.</p> </blockquote> <p class="book">В этих трех коротких абзацах Ричард Столмен (Richard Stallman) выразил суть важных принципов управления динамической памятью с помощью <code>malloc()</code>. Именно использование динамической памяти и принцип «никаких произвольных ограничений» делают программы GNU такими устойчивыми и более работоспособными по сравнению с их Unix-двойниками.</p> <p class="book">Мы хотим подчеркнуть, что стандарт С требует, чтобы <code>realloc()</code> <i>не</i> разрушал оригинальный блок памяти, если она возвращает <code>NULL</code>.</p> <a name="t52"></a><h3 class="book">3.2.1.7. Использование персональных программ распределения<br> </h3> <p class="book">Набор функций с <code>malloc()</code> является набором общего назначения по выделению памяти. Он должен быть способен обработать запросы на произвольно большие или маленькие размеры памяти и осуществлять все необходимые учетные действия при освобождении различных участков выделенной памяти. Если ваша программа выделяет значительную динамическую память, вы можете обнаружить, что она тратит большую часть своего времени в функциях <code>malloc()</code>.</p> <p class="book">Вы можете написать <i>персональную программу распределения</i> — набор функций или макросов, которые выделяют большие участки памяти с помощью <code>malloc()</code>, а затем дробят их на маленькие кусочки по одному за раз. Эта методика особенно полезна, если вы выделяете множество отдельных экземпляров одной и той же сравнительно небольшой структуры.</p> <p class="book">Например, GNU awk (gawk) использует эту методику. Выдержка из файла <code>awk.h</code> в дистрибутиве <code>gawk</code> (слегка отредактировано, чтобы уместилось на странице):</p> <p class="book"><code>#define getnode(n) if (nextfree) n = nextfree, \</code></p> <p class="book"><code>&nbsp;nextfree = nextfree-&gt;nextp; else n = more_nodes()</code></p> <p class="book"><code>#define freenode(n) ((n)-&gt;flags = 0, (n)-&gt;exec_count = 0,\</code></p> <p class="book"><code>&nbsp;(n)-&gt;nextp = nextfree, nextfree = (n))</code></p> <p class="book">Переменная <code>nextfree</code> указывает на связанный список структур NODE. Макрос <code>getnode()</code> убирает из списка первую структуру, если она там есть. В противном случае она вызывает <code>more_nodes()</code>, чтобы выделить новый список свободных структур <code>NODE</code>. Макрос <code>freenode()</code> освобождает структуру <code>NODE</code>, помещая его в начало списка.</p> <blockquote class="book"><p class="book"><b>ЗАМЕЧАНИЕ</b>. Первоначально при написании своего приложения делайте это простым способом: непосредственно используйте <code>malloc()</code> и <code>free()</code>. Написание собственного распределителя вы должны рассмотреть лишь в том и только в том случае, если профилирование вашей программы покажет, что она значительную часть времени проводит в функциях выделения памяти.</p> </blockquote> <a name="t53"></a><h3 class="book">3.2.1.8. Пример: чтение строк произвольной длины<br> </h3> <p class="book">Поскольку это, в конце концов, <i>Программирование на Linux в примерах</i>, настало время для примера из реальной жизни. Следующий код является функцией <code>readline()</code> из GNU Make 3.80 (<code>ftp://ftp.gnu.org/gnu/make/make-3.80.tar.gz</code>). Ее можно найти в файле <code>read.c</code>.</p> <p class="book">Следуя принципу «никаких произвольных ограничений», строки в <code>Makefile</code> могут быть любой длины. Поэтому главной задачей этой процедуры является чтение строк произвольной длины и гарантирование того, что они помещаются в используемый буфер.</p> <p class="book">Вторичной задачей является распоряжение продлением строк. Как и в С, строки, заканчивающиеся обратным слешем, логически продолжаются со следующей строки. Используется стратегия поддержания буфера. В нем хранится столько строк, сколько помещается в буфер, причем указатели отслеживают начало буфера, текущую строку и следующую строку. Вот структура:</p> <p class="book"><code>struct ebuffer {</code></p> <p class="book"><code>&nbsp;char *buffer;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Начало текущей строки в буфере. */</code></p> <p class="book"><code>&nbsp;char *bufnext;&nbsp;&nbsp;&nbsp;&nbsp; /* Начало следующей строки в буфере. */</code></p> <p class="book"><code>&nbsp;char *bufstart;&nbsp;&nbsp;&nbsp; /* Начало всего буфера. */</code></p> <p class="book"><code>&nbsp;unsigned int size; /* Размер буфера для malloc. */</code></p> <p class="book"><code>&nbsp;FILE *fp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;/* Файл или NULL, если это внутренний буфер. */</code></p> <p class="book"><code>&nbsp;struct floc floc;&nbsp; /* Информация о файле в fp (если он есть). */</code></p> <p class="book"><code>};</code></p> <p class="book">Поле <code>size</code> отслеживает размер всего буфера, a <code>fp</code> является указателем типа <code>FILE</code> для файла ввода. Структура floc не представляет интереса при изучении процедуры.</p> <p class="book">Функция возвращает число строк в буфере. (Номера строк здесь даны относительно начала функции, а не исходного файла.)</p> <p class="book"><code>1&nbsp; static long</code></p> <p class="book"><code>2&nbsp; readline(ebuf) /* static long readline(struct ebuffer *ebuf) */</code></p> <p class="book"><code>3&nbsp; struct ebuffer *ebuf;</code></p> <p class="book"><code>4&nbsp; {</code></p> <p class="book"><code>5&nbsp;&nbsp; char *p;</code></p> <p class="book"><code>6&nbsp;&nbsp; char *end;</code></p> <p class="book"><code>7&nbsp;&nbsp; char *start;</code></p> <p class="book"><code>8&nbsp;&nbsp; long nlines = 0;</code></p> <p class="book"><code>9</code></p> <p class="book"><code>10&nbsp; /* Использование строковых буферов и буферов потоков достаточно</code></p> <p class="book"><code>11&nbsp;&nbsp;&nbsp;&nbsp; различается, чтобы использовать разные функции. */</code></p> <p class="book"><code>12</code></p> <p class="book"><code>13&nbsp; if (!ebuf-&gt;fp)</code></p> <p class="book"><code>14&nbsp;&nbsp; return readstring(ebuf);</code></p> <p class="book"><code>15</code></p> <p class="book"><code>16&nbsp; /* При чтении из файла для каждой новой строки мы всегда</code></p> <p class="book"><code>17&nbsp;&nbsp;&nbsp;&nbsp; начинаем с начала буфера. */</code></p> <p class="book"><code>18</code></p> <p class="book"><code>19&nbsp; p = start = ebuf-&gt;bufstart;</code></p> <p class="book"><code>20&nbsp; end = p + ebuf-&gt;size;</code></p> <p class="book"><code>21&nbsp; *p = '\0';</code></p> <p class="book">Для начала заметим, что GNU Make написан на С K&amp;R для максимальной переносимости. В исходной части объявляются переменные, и если ввод осуществляется из строки (как в случае расширения макроса), код вызывает другую функцию, <code>readstring()</code> (строки 13 и 14). Строка '<code>!ebuf-&gt;fp</code>' (строка 13) является более короткой (и менее понятной, по нашему мнению) проверкой на пустой указатель; это то же самое, что и '<code>ebuf-&gt;fp==NULL</code>'.</p> <p class="book">Строки 19-21 инициализируют указатели и вводят байт NUL, который является символом завершения строки С в конце буфера. Затем функция входит в цикл (строки 23–95), который продолжается до завершения всего ввода.</p> <p class="book"><code>23 while (fgets(p, end - р, ebuf-&gt;fp) != 0)</code></p> <p class="book"><code>24 {</code></p> <p class="book"><code>25&nbsp; char *p2;</code></p> <p class="book"><code>26&nbsp; unsigned long len;</code></p> <p class="book"><code>27&nbsp; int backslash;</code></p> <p class="book"><code>28</code></p> <p class="book"><code>29&nbsp; len = strlen(p);</code></p> <p class="book"><code>30&nbsp; if (len == 0)</code></p> <p class="book"><code>31&nbsp; {</code></p> <p class="book"><code>32&nbsp;&nbsp; /* Это случается лишь тогда, когда первый символ строки '\0'.</code></p> <p class="book"><code>33&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Это довольно безнадежный случай, но (верите или нет) ляп Афины</code></p> <p class="book"><code>34&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; бьет снова! (xmkmf помещает NUL в свои makefile.)</code></p> <p class="book"><code>35&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Здесь на самом деле нечего делать; мы создаем новую строку, чтобы</code></p> <p class="book"><code>36&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; следующая строка не была частью данной строки. */</code></p> <p class="book"><code>37&nbsp;&nbsp;&nbsp;error (&amp;ebuf-&gt;floc,</code></p> <p class="book"><code>38&nbsp;&nbsp;&nbsp; _("warning: NUL character seen; rest of line ignored"));</code></p> <p class="book"><code>39&nbsp;&nbsp; p[0] = '\n';</code></p> <p class="book"><code>40&nbsp;&nbsp; len = l;</code></p> <p class="book"><code>41&nbsp; }</code></p> <p class="book">Функция <code>fgets()</code> (строка 23) принимает указатель на буфер, количество байтов для прочтения и переменную <code>FILE*</code> для файла, из которого осуществляется чтение. Она читает на один байт меньше указанного, чтобы можно было завершить буфер символом '<code>\0</code>'. Эта функция подходит, поскольку она позволяет избежать переполнения буфера. Она прекращает чтение, когда встречается с символами конца строки или конца файла; если это символ новой строки, он помещается в буфер. Функция возвращает <code>NULL</code> при неудаче или значение указателя первого аргумента при успешном завершении.</p> <p class="book">В этом случае аргументами являются указатель на свободную область буфера, размер оставшейся части буфера и указатель <code>FILE</code> для чтения.</p> <p class="book">Комментарии в строках 32–36 очевидны; если встречается нулевой байт, программа выводит сообщение об ошибке и представляет вывод как пустую строку. После компенсирования нулевого байта (строки 30–41) код продолжает работу.</p> <p class="book"><code>43 /* Обойти только что прочитанный текст. */</code></p> <p class="book"><code>44 p += len;</code></p> <p class="book"><code>45</code></p> <p class="book"><code>46 /* Если последний символ - не конец строки, она не поместилась</code></p> <p class="book"><code>47&nbsp;&nbsp;&nbsp; целиком в буфер. Увеличить буфер и попытаться снова. */</code></p> <p class="book"><code>48 if (p[-1] != '\n')</code></p> <p class="book"><code>49&nbsp; goto more_buffer;</code></p> <p class="book"><code>50</code></p> <p class="book"><code>51 /* Мы получили новую строку, увеличить число строк. */</code></p> <p class="book"><code>52 ++nlines;</code></p> <p class="book">Строки 43–52 увеличивают указатель на участок буфера за только что прочитанными данными. Затем код проверяет, является ли последний прочитанный символ символом конца строки. Конструкция <code>p[-1]</code> (строка 48) проверяет символ перед p, также как <code>p[0]</code> является текущим символом, а <code>p[1]</code> — следующим. Сначала это кажется странным, но если вы переведете это на язык математики указателей, <code>*(p-1)</code>, это приобретет больший смысл, а индексированная форма, возможно, проще для чтения.</p> <p class="book">Если последний символ не был символом конца строки, это означает, что нам не хватило места, и код выходит (с помощью <code>goto</code>) для увеличения размера буфера (строка 49). В противном случае увеличивается число строк.</p> <p class="book"><code>54 #if !defined(WINDOWS32) &amp;&amp; !defined(__MSDOS__)</code></p> <p class="book"><code>55 /* Проверить, что строка завершилась CRLF; если так,</code></p> <p class="book"><code>56&nbsp;&nbsp;&nbsp; игнорировать CR. */</code></p> <p class="book"><code>57 if ((p - start) &gt; 1 &amp;&amp; p[-2] == '\r')</code></p> <p class="book"><code>58 {</code></p> <p class="book"><code>59&nbsp; --p;</code></p> <p class="book"><code>60&nbsp; p[-1] = '\n';</code></p> <p class="book"><code>61 }</code></p> <p class="book"><code>62 #endif</code></p> <p class="book">Строки 54–62 обрабатывают вводимые строки, следующие соглашению Microsoft по завершению строк комбинацией символов возврата каретки и перевода строки (CR-LF), а не просто символом перевода строки (новой строки), который является соглашением Linux/Unix. Обратите внимание, что <code>#ifdef</code> <i>исключает</i> этот код на платформе Microsoft, очевидно, библиотека <code>&lt;stdio.h&gt;</code> на этих системах автоматически осуществляет это преобразование. Это верно также для других не-Unix систем, поддерживающих стандартный С.</p> <p class="book"><code>64&nbsp; backslash = 0;</code></p> <p class="book"><code>65&nbsp; for (p2 = p - 2; p2 &gt;= start; --p2)</code></p> <p class="book"><code>66&nbsp; {</code></p> <p class="book"><code>67&nbsp;&nbsp; if (*p2 != '\\')</code></p> <p class="book"><code>68&nbsp;&nbsp; break;</code></p> <p class="book"><code>69&nbsp;&nbsp; backslash = !backslash;</code></p> <p class="book"><code>70&nbsp; }</code></p> <p class="book"><code>71</code></p> <p class="book"><code>72&nbsp; if (!backslash)</code></p> <p class="book"><code>73&nbsp; {</code></p> <p class="book"><code>74&nbsp;&nbsp; p[-1] = '\0';</code></p> <p class="book"><code>75&nbsp;&nbsp; break;</code></p> <p class="book"><code>76&nbsp; }</code></p> <p class="book"><code>77</code></p> <p class="book"><code>78&nbsp; /* Это была комбинация обратный слеш/новая строка. Если есть</code></p> <p class="book"><code>79&nbsp;&nbsp; &nbsp; место, прочесть еще одну строку. */</code></p> <p class="book"><code>80&nbsp; if (end - p &gt;= 80)</code></p> <p class="book"><code>81&nbsp;&nbsp; continue;</code></p> <p class="book"><code>82</code></p> <p class="book"><code>83&nbsp; /* В конце буфера нужно больше места, поэтому выделить еще.</code></p> <p class="book"><code>84&nbsp; &nbsp;&nbsp; Позаботиться о сохранении текущего смещения в p. */</code></p> <p class="book"><code>85 more_buffer:</code></p> <p class="book"><code>86&nbsp; {</code></p> <p class="book"><code>87&nbsp;&nbsp; unsigned long off = p - start;</code></p> <p class="book"><code>88&nbsp;&nbsp; ebuf-&gt;size *= 2;</code></p> <p class="book"><code>89&nbsp;&nbsp; start = ebuf-&gt;buffer=ebuf-&gt;bufstart=(char*)xrealloc(start,</code></p> <p class="book"><code>90&nbsp;&nbsp;&nbsp; ebuf-&gt;size);</code></p> <p class="book"><code>91&nbsp;&nbsp; p = start + off;</code></p> <p class="book"><code>92&nbsp;&nbsp; end = start + ebuf-&gt;size;</code></p> <p class="book"><code>93&nbsp;&nbsp; *p = '\0';</code></p> <p class="book"><code>94&nbsp; }</code></p> <p class="book"><code>95 }</code></p> <p class="book">До сих пор мы имели дело с механизмом получения в буфер по крайней мере одной полной строки. Следующий участок обрабатывает случай строки с продолжением. Хотя он должен гарантировать, что конечный символ обратного слеша не является частью нескольких обратных слешей в конце строки. Код проверяет, является ли общее число таких символов четным или нечетным путем простого переключения переменной <code>backslash</code> из 0 в 1 и обратно. (Строки 64–70.)</p> <p class="book">Если число четное, условие '<code>!backshlash</code>' (строка 72) будет истинным. В этом случае конечный символ конца строки замещается байтом NUL, и код выходит из цикла.</p> <p class="book">С другой стороны, если число нечетно, строка содержит четное число пар обратных слешей (представляющих символы \\, как в С), и конечную комбинацию символов обратного слеша и конца строки.<sup><a name="anotelink43" id="anotelink43" href="#n43" title=" Этот код несет с собой аромат практического опыта, не удивительно было узнать, что более ранние версии просто проверяли наличие обратного слеша перед символом конца строки, пока кто-то не пожаловался, что он не работает, когда в конце строки есть несколько обратных слешей — Примеч. автора.">[43]</a></sup> В этом случае, если в буфере остались по крайней мере 80 свободных байтов, программа продолжает чтение в цикле следующей строки (строки 78–81). (Использование магического числа 80 не очень здорово; было бы лучше определить и использовать макроподстановку.)</p> <p class="book">По достижении строки 83 программе нужно больше места в буфере. Именно здесь вступает в игру динамическое управление памятью. Обратите внимание на комментарий относительно сохранения значения <code>p</code> (строки 83-84); мы обсуждали это ранее в терминах повторной инициализации указателей для динамической памяти. Значение end также устанавливается повторно. Строка 89 изменяет размер памяти.</p> <p class="book">Обратите внимание, что здесь вызывается функция <code>xrealloc()</code>. Многие программы GNU используют вместо <code>malloc()</code> и <code>realloc()</code> функции-оболочки, которые автоматически выводят сообщение об ошибке и завершают программу, когда стандартные процедуры возвращают <code>NULL</code>. Такая функция-оболочка может выглядеть таким образом:</p> <p class="book"><code>extern const char *myname; /* установлено в main() */</code></p> <br> <p class="book"><code>void *xrealloc(void *ptr, size_t amount) {</code></p> <p class="book"><code>&nbsp;void *p = realloc(ptr, amount);</code></p> <p class="book"><code>&nbsp;if (p == NULL) {</code></p> <p class="book"><code>&nbsp;&nbsp;fprintf(stderr, "%s: out of memory!\n", myname);</code></p> <p class="book"><code>&nbsp;&nbsp;exit(1);</code></p> <p class="book"><code>&nbsp;}</code></p> <p class="book"><code>&nbsp;return p;</code></p> <p class="book"><code>}</code></p> <p class="book">Таким образом, если функция <code>xrealloc()</code> возвращается, она гарантированно возвращает действительный указатель. (Эта стратегия соответствует принципу «проверки каждого вызова на ошибки», избегая в то же время беспорядка в коде, который происходит при таких проверках с непосредственным использованием стандартных процедур.) Вдобавок, это позволяет эффективно использовать конструкцию '<code>ptr = xrealloc(ptr, new_size)</code>', против которой мы предостерегали ранее.</p> <p class="book">Обратите внимание, что не всегда подходит использование такой оболочки. Если вы сами хотите обработать ошибки, не следует использовать оболочку. С другой стороны, если нехватка памяти всегда является фатальной ошибкой, такая оболочка вполне удобна.</p> <p class="book"><code>97&nbsp;&nbsp; if (ferror(ebuf-&gt;fp))</code></p> <p class="book"><code>98 &nbsp;&nbsp; pfatal_with_name(ebuf-&gt;floc.filenm);</code></p> <p class="book"><code>99</code></p> <p class="book"><code>100&nbsp; /* Если обнаружено несколько строк, возвратить их число.</code></p> <p class="book"><code>101&nbsp;&nbsp; &nbsp; Если не несколько, но _что-то_ нашли, значит, прочитана</code></p> <p class="book"><code>102&nbsp;&nbsp;&nbsp; &nbsp;последняя строка файла без завершающего символа конца</code></p> <p class="book"><code>103&nbsp;&nbsp;&nbsp;&nbsp; строки; вернуть 1. Если ничего не прочитано, это EOF;</code></p> <p class="book"><code>104&nbsp;&nbsp;&nbsp;&nbsp; возвратить -1. */</code></p> <p class="book"><code>105&nbsp; return nlines ? nlines : p == ebuf-&gt;bufstart ? -1 : 1;</code></p> <p class="book"><code>106 }</code></p> <p class="book">В заключение, функция <code>readline()</code> проверяет ошибки ввода/вывода, а затем возвращает описательное значение. Функция <code>pfatal_with_name()</code> (строка 98) не возвращается.<sup><a name="anotelink44" id="anotelink44" href="#n44" title=" Эта функция завершает выполнение программы — Примеч. науч. ред.">[44]</a></sup></p> <a name="t54"></a><h3 class="book">3.2.1.9. Только GLIBC: чтение целых строк: <code>getline()</code> и <code>getdelim()</code><br> </h3> <p class="book">Теперь, когда вы увидели, как читать строки произвольной длины, вы можете сделать вздох облегчения, что вам не нужно самим писать такую функцию. GLIBC предоставляет вам для этого две функции:</p> <p class="book"><code>#define _GNU_SOURCE 1 /* GLIBC */</code></p> <p class="book"><code>#include &lt;stdio.h&gt;</code></p> <p class="book"><code>#include &lt;sys/types.h&gt; /* для ssize_t */</code></p> <br> <p class="book"><code>ssize_t getline(char **lineptr, size_t *n, FILE *stream);</code></p> <p class="book"><code>ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);</code></p> <p class="book">Определение константы <code>_GNU_SOURCE</code> вводит объявления функций <code>getline()</code> и <code>getdelim()</code>. В противном случае они неявно объявлены как возвращающие <code>int</code>. Для объявления возвращаемого типа <code>ssize_t</code> нужен файл <code>&lt;sys/types.h&gt;</code>. (<code>ssize_t</code> является «знаковым <code>size_t</code>». Он предназначен для такого же использования, что и <code>size_t</code>, но в местах, где может понадобиться использование также и отрицательных значений.)</p> <p class="book">Обе функции управляют для вас динамической памятью, гарантируя, что буфер, содержащий входную строку, достаточно большой для размещения всей строки. Их отличие друг от друга в том, что <code>getline()</code> читает до символа конца строки, a <code>getdelim()</code> использует в качестве разделителя символ, предоставленный пользователем. Общие аргументы следующие:</p> <p class="book"><code>char **lineptr</code></p> <p class="book">Указатель на <code>char*</code> указатель для адреса динамически выделенного буфера. Чтобы <code>getline()</code> сделала всю работу, он должен быть инициализирован <code>NULL</code>. В противном случае, он должен указывать на область памяти, выделенную с помощью <code>malloc()</code>.</p> <p class="book"><code>size_t *n</code></p> <p class="book">Указатель на размер буфера. Если вы выделяете свой собственный буфер, <code>*n</code> должно содержать размер буфера. Обе функции обновляют <code>*n</code> новым значением размера буфера, если они его изменяют.</p> <p class="book"><code>FILE* stream</code></p> <p class="book">Место, откуда следует получать входные символы.</p> <p class="book">По достижении конца файла или при ошибке функция возвращает -1. Строки содержат завершающий символ конца строки или разделитель (если он есть), а также завершающий нулевой байт. Использование <code>getline()</code> просто, как показано в <code>ch03-getline.с</code>:</p> <p class="book"><code>/* ch03-getline.c --- демонстрация getline(). */</code></p> <p class="book"><code>#define _GNU_SOURCE 1</code></p> <p class="book"><code>#include &lt;stdio.h&gt;</code></p> <p class="book"><code>#include &lt;sys/types.h&gt;</code></p> <br> <p class="book"><code>/* main - прочесть строку и отобразить ее, пока не достигнут EOF */</code></p> <p class="book"><code>int main(void) {</code></p> <p class="book"><code>&nbsp;char *line = NULL;</code></p> <p class="book"><code>&nbsp;size_t size = 0;</code></p> <p class="book"><code>&nbsp;ssize_t ret;</code></p> <p class="book"><code>&nbsp;while ((ret = getline(&amp;line, &amp;size, stdin)) != -1)</code></p> <p class="book"><code>&nbsp; printf("(%lu) %s", size, line);</code></p> <p class="book"><code>&nbsp;return 0;</code></p> <p class="book"><code>}</code></p> <p class="book">Вот эта функция в действии, показывающая размер буфера. Третья входная и выходная строки намеренно длинные, чтобы заставить <code>getline()</code> увеличить размер буфера:</p> <p class="book"><code>$ <b>ch03-getline</b> /* Запустить программу */</code></p> <p class="book"><code><b>this is a line</b></code></p> <p class="book"><code>(120) this is a line</code></p> <p class="book"><code><b>And another line.</b></code></p> <p class="book"><code>(120) And another line.</code></p> <p class="book"><code><b>A llllllllllllllllloooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnngnnnggggggggggg llliiiiiiiiiiiiiiiiiiinnnnnnnnnnnnnnnnnnnneeeeeeeeee</b></code></p> <p class="book"><code>(240) A llllllllllllllllloooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnngnnnggggggggggg llliiiiiiiiiiiiiiiiiiinnnnnnnnnnnnnnnnnnnneeeeeeeeee</code></p><a name="t55"></a><h3 class="book"> 3.2.2. Копирование строк: <code>strdup()</code><br></h3><p class="book">Одной чрезвычайно типичной операцией является выделение памяти для копирования строки. Это настолько типично, что многие программисты предусматривают для нее простую функцию вместо использования внутритекстового кодирования, и часто эта функция называется <code>strdup()</code>:</p><p class="book"><code>#include &lt;string.h&gt;</code></p><br><p class="book"><code>/* strdup --- выделить память с malloc() и скопировать строку */</code></p><p class="book"><code>char *strdup(const char *str) {</code></p><p class="book"><code>&nbsp;size_t len;</code></p><p class="book"><code>&nbsp;char *copy;</code></p><p class="book"><code>&nbsp;len = strlen(str) + 1;</code></p><p class="book"><code>&nbsp;/* включить место для завершающего '\0' */</code></p><p class="book"><code>&nbsp;copy = malloc(len);</code></p><p class="book"><code>&nbsp;if (copy != NULL) strcpy(copy, str);</code></p><p class="book"><code>&nbsp;return copy; /* при ошибке возвращает NULL */</code></p><p class="book"><code>}</code></p><p class="book">С появлением стандарта POSIX 2001 программисты по всему миру могут вздохнуть свободнее: эта функция является теперь частью POSIX в виде расширения XSI:</p><p class="book"><code>#include &lt;string.h&gt; /* XSI */</code></p><br><p class="book"><code>char *strdup(const char *str); /* Копировать str */</code></p><p class="book">Возвращаемое значение равно <code>NULL</code>, если была ошибка, или указатель на динамически выделенную память с копией <code>str</code>. Возвращенное значение должно быть освобождено с помощью <code>free()</code>, когда больше не требуется.</p><a name="t56"></a><h3 class="book"> 3.2.3. Системные вызовы: <code>brk()</code> и <code>sbrk()</code><br></h3><p class="book">Четыре функции, которые мы рассмотрели (<code>malloc()</code>, <code>calloc()</code>, <code>realloc()</code> и <code>free()</code>) являются стандартными, переносимыми функциями для управления динамической памятью.</p><p class="book">На Unix-системах стандартные функции реализованы поверх двух дополнительных, очень примитивных процедур, которые непосредственно изменяют размер адресного пространства процесса. Мы представляем их здесь, чтобы помочь вам понять, как работают GNU/Linux и Unix (снова «под капотом»); крайне маловероятно, что вам когда-нибудь понадобится использовать эти функции в обычных программах. Они определены следующим образом:</p><p class="book"><code>#include &lt;unistd.h&gt; /* Обычный */</code></p><p class="book"><code>#include &lt;malloc.h&gt; /* Необходим для систем GLIBC 2 */</code></p><br><p class="book"><code>int brk(void *end_data_segment);</code></p><p class="book"><code>void *sbrk(ptrdiff_t increment);</code></p><p class="book">Системный вызов <code>brk()</code> действительно изменяет адресное пространство процесса. Адрес является указателем, представляющим окончание сегмента данных (на самом деле, области кучи, как было показано ранее на рис. 3.1). Ее аргумент является абсолютным логическим адресом, представляющим новое окончание адресного пространства. В случае успеха функция возвращает 0, а в случае неуспеха (-1).</p><p class="book">Функцию <code>sbrk()</code> использовать проще; ее аргумент является числом байтов, на которое нужно увеличить адресное пространство. Вызвав ее с приращением 0, можно определить, где в настоящее время заканчивается адресное пространство. Таким образом, чтобы увеличить адресное пространство на 32 байта, используется код следующего вида:</p><p class="book"><code>char *p = (char*)sbrk(0); /* получить текущий конец адресного</code></p><p class="book"><code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; пространства */</code></p><p class="book"><code>if (brk(p + 32) &lt; 0) {</code></p><p class="book"><code>&nbsp;/* обработать ошибку */</code></p><p class="book"><code>}</code></p><p class="book"><code>/* в противном случае, изменение сработало */</code></p><p class="book">Практически, вам не нужно непосредственно использовать <code>brk()</code>. Вместо этого используется исключительно <code>sbrk()</code> для увеличения (или даже сокращения) адресного пространства. (Вскоре мы покажем, как это делать, в разделе 3.2.5. «Исследование адресного пространства».)</p><p class="book">Еще более практично вообще <i>никогда</i> не использовать эти процедуры. Программа, которая их использует, не может затем использовать также и <code>malloc()</code>, и это создает большую проблему, поскольку многие элементы стандартной библиотеки полагаются на использование <code>malloc()</code>. Поэтому использование <code>brk()</code> или <code>sbrk()</code> может приводить к трудно обнаруживаемым крушениям программы.</p><p class="book">Но знать о низкоуровневых механизмах стоит, и конечно же, набор функций <code>malloc()</code> реализован с помощью <code>sbrk()</code> и <code>brk()</code>.</p><a name="t57"></a><h3 class="book"> 3.2.4. Вызовы ленивых программистов: <code>alloca()</code><br></h3><blockquote class="epigraph"> <p class="book">«Опасность, Билл Робинсон! Опасность!»</p> <blockquote class="book"><i>- Робот -</i></blockquote></blockquote><p class="book">Есть еще одна дополнительная функция выделения памяти, о которой вам нужно знать. Мы обсуждаем ее лишь для того, чтобы вы поняли ее, когда увидите, но не следует использовать ее в новых программах! Эта функция называется <code>alloca()</code>; она объявлена следующим образом:</p><p class="book"><code>/* Заголовок в GNU/Linux, возможно, не на всех Unix-системах */</code></p><p class="book"><code>#include &lt;alloca.h&gt; /* Обычный */</code></p><br><p class="book"><code>void *alloca(size_t size);</code></p><p class="book">Функция <code>alloca()</code> выделяет <code>size</code> байтов из <i>стека</i>. Хорошо, что выделенная память исчезает после возвращения из функции. Нет необходимости явным образом освобождать память, поскольку это осуществляется автоматически, как в случае с локальными переменными.</p><p class="book">На первый взгляд, <code>alloca()</code> выглядит чем-то типа панацеи для программистов, можно выделять память, о которой можно вовсе не беспокоиться. Подобно Темной Стороне Силы, это, конечно, привлекает. И подобным же образом этого нужно избегать по следующим причинам:</p><p class="book">•&nbsp;Функция не является стандартной; она не включена ни в какой стандарт, ни в ISO, ни в С или POSIX.</p><p class="book">•&nbsp;Функция не переносима. Хотя она существует на многих системах Unix и GNU/Linux, она не существует на не-Unix системах. Это проблема, поскольку код часто должен быть многоплатформенным, выходя за пределы просто Linux и Unix.</p><p class="book">•&nbsp;На некоторых системах <code>alloca()</code> невозможно даже реализовать. Весь мир не является ни процессором Intel x86, ни GCC.</p><p class="book">•&nbsp;Цитируя справку<sup><a name="anotelink45" id="anotelink45" href="#n45" title=" alloca(3) — Примеч. науч. ред.">[45]</a></sup> (добавлено выделение): «Функция <code>alloca</code> зависит от машины и от компилятора. <i>На многих системах ее реализация ошибочна.</i> Ее использование не рекомендуется».</p><p class="book">•&nbsp;Снова цитируя справку: «На многих системах <code>alloca</code> не может быть использована внутри списка аргументов вызова функции, поскольку резервируемая в стеке при помощи <code>alloca</code> память оказалась бы в середине стека в пространстве для аргументов функции».</p><p class="book">•&nbsp;Она потворствует неряшливому программированию. Тщательная и корректная работа с памятью не сложна; вам просто нужно подумать о том, что вы делаете, и планировать заранее.</p><p class="book">GCC обычно использует встроенную версию функции, которая действует с использованием внутритекстового (inline) кода. В результате есть другие последствия <code>alloca()</code>. Снова цитируя справку:</p><blockquote class="book"> <p class="book">Факт, что код является внутритекстовым (inline), означает, что невозможно получить адрес этой функции или изменить ее поведение путем компоновки с другой библиотекой.</p> <p class="book">Внутритекстовый код часто состоит из одной инструкции, подгоняющей указатель стека, и не проверяет переполнение стека. Поэтому нет возврата <code>NULL</code> при ошибке.</p></blockquote><p class="book">Справочная страница не углубляется в описание проблемы со встроенной <code>alloca()</code> GCC. Если есть переполнение стека, возвращаемое значение является <i>мусором</i>. И у вас нет способа сообщить об этом! Это упущение делает невозможным использование GCC <code>alloca()</code> в устойчивом коде.</p><p class="book">Все это должно убедить вас избегать <code>alloca()</code> в любом новом коде, который вы пишете. В любом случае, если приходится писать переносимый код с использованием <code>malloc()</code> и <code>free()</code>, нет причины в использовании также и <code>alloca()</code>.</p><a name="t58"></a><h3 class="book"> 3.2.5. Исследование адресного пространства<br></h3><p class="book">Следующая программа, <code>ch03-memaddr.c</code>, подводит итог всему, что мы узнали об адресном пространстве. Она делает множество вещей, которые не следует делать на практике, таких, как вызовы <code>alloca()</code> или непосредственные вызовы <code>brk()</code> и <code>sbrk()</code>.</p><p class="book"><code>1&nbsp; /*</code></p><p class="book"><code>2&nbsp;&nbsp; * ch03-memaddr.с --- Показать адреса секций кода, данных и стека,</code></p><p class="book"><code>3&nbsp;&nbsp; * а также BSS и динамической памяти.</code></p><p class="book"><code>4&nbsp;&nbsp; */</code></p><p class="book"><code>5</code></p><p class="book"><code>6&nbsp; #include &lt;stdio.h&gt;</code></p><p class="book"><code>7&nbsp; #include &lt;malloc.h&gt; /* для определения ptrdiff_t в GLIBC */</code></p><p class="book"><code>8&nbsp; #include &lt;unistd.h&gt;</code></p><p class="book"><code>9&nbsp; #include &lt;alloca.h&gt; /* лишь для демонстрации */</code></p><p class="book"><code>10</code></p><p class="book"><code>11 extern void afunc(void); /* функция, показывающая рост стека */</code></p><p class="book"><code>12</code></p><p class="book"><code>13 int bss_var; /* автоматически инициализируется в 0, должна быть в BSS */</code></p><p class="book"><code>14 int data_var = 42; /* инициализируется в не 0, должна быть</code></p><p class="book"><code>15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; в сегменте данных */</code></p><p class="book"><code>16 int</code></p><p class="book"><code>17 main(int argc, char **argv) /* аргументы не используются */</code></p><p class="book"><code>18 {</code></p><p class="book"><code>19 &nbsp;char *p, *b, *nb;</code></p><p class="book"><code>20</code></p><p class="book"><code>21&nbsp; printf("Text Locations:\n");</code></p><p class="book"><code>22&nbsp; printf("\tAddress of main: %p\n", main);</code></p><p class="book"><code>23 &nbsp;printf("\tAddress of afunc: %p\n", afunc);</code></p><p class="book"><code>24</code></p><p class="book"><code>25&nbsp; printf("Stack Locations.\n");</code></p><p class="book"><code>26 &nbsp;afunc();</code></p><p class="book"><code>27</code></p><p class="book"><code>28&nbsp; p = (char*)alloca(32);</code></p><p class="book"><code>29&nbsp; if (p != NULL) {</code></p><p class="book"><code>30&nbsp;&nbsp; printf("\tStart of alloca()'ed array: %p\n", p);</code></p><p class="book"><code>31&nbsp;&nbsp; printf("\tEnd of alloca()'ed array: %p\n", p + 31);</code></p><p class="book"><code>32 &nbsp;}</code></p><p class="book"><code>33</code></p><p class="book"><code>34&nbsp; printf("Data Locations:\n");</code></p><p class="book"><code>35&nbsp; printf("\tAddress of data_var: %p\n", &amp;data_var);</code></p><p class="book"><code>36</code></p><p class="book"><code>37&nbsp; printf("BSS Locations:\n");</code></p><p class="book"><code>38&nbsp; printf("\tAddress of bss_var: %p\n", &amp;bss_var);</code></p><p class="book"><code>39</code></p><p class="book"><code>40&nbsp; b = sbrk((ptrdiff_t)32); /* увеличить адресное пространство */</code></p><p class="book"><code>41&nbsp; nb = sbrk((ptrdiff_t)0);</code></p><p class="book"><code>42&nbsp; printf("Heap Locations:\n");</code></p><p class="book"><code>43&nbsp; printf("\tInitial end of heap: %p\n", b);</code></p><p class="book"><code>44&nbsp; printf("\tNew end of heap: %p\n", nb);</code></p><p class="book"><code>45</code></p><p class="book"><code>46&nbsp; b = sbrk((ptrdiff_t)-16); /* сократить его */</code></p><p class="book"><code>47&nbsp; nb = sbrk((ptrdiff_t)0);</code></p><p class="book"><code>48&nbsp; printf("\tFinal end of heap: %p\n", nb);</code></p><p class="book"><code>49 }</code></p><p class="book"><code>50</code></p><p class="book"><code>51 void</code></p><p class="book"><code>52 afunc(void)</code></p><p class="book"><code>53 {</code></p><p class="book"><code>54 &nbsp;static int level = 0; /* уровень рекурсии */</code></p><p class="book"><code>55&nbsp; auto int stack_var; /* автоматическая переменная в стеке */</code></p><p class="book"><code>56</code></p><p class="book"><code>57 &nbsp;if (++level == 3) /* избежать бесконечной рекурсии */</code></p><p class="book"><code>58 &nbsp; return;</code></p><p class="book"><code>59</code></p><p class="book"><code>60&nbsp; printf("\tStack level %d: address of stack_var: %p\n",</code></p><p class="book"><code>61&nbsp;&nbsp; level, &amp;stack_var);</code></p><p class="book"><code>62&nbsp; afunc(); /* рекурсивный вызов */</code></p><p class="book"><code>63 }</code></p><p class="book">Эта программа распечатывает местонахождение двух функций <code>main()</code> и <code>afunc()</code> (строки 22–23). Затем она показывает, как стек растет вниз, позволяя <code>afunc()</code> (строки 51–63) распечатать адреса последовательных экземпляров ее локальной переменной <code>stack_var</code>. (<code>stack_var</code> намеренно объявлена как <code>auto</code>, чтобы подчеркнуть, что она находится в стеке.) Затем она показывает расположение памяти, выделенной с помощью <code>alloca()</code> (строки 28–32). В заключение она печатает местоположение переменных данных и BSS (строки 34–38), а затем памяти, выделенной непосредственно через <code>sbrk()</code> (строки 40–48). Вот результаты запуска программы на системе Intel GNU/Linux:</p><p class="book"><code>$ <b>ch03-memaddr</b></code></p><p class="book"><code>Text Locations:</code></p><p class="book"><code>&nbsp;Address of main: 0x804838c</code></p><p class="book"><code>&nbsp;Address of afunc: 0x80484a8</code></p><p class="book"><code>Stack Locations:</code></p><p class="book"><code>&nbsp;Stack level 1: address of stack_var: 0xbffff864</code></p><p class="book"><code>&nbsp;Stack level 2: address of stack_var: 0xbffff844</code></p><p class="book"><code>&nbsp; /* Стек растет вниз */</code></p><p class="book"><code>&nbsp;Start of alloca()'ed array: 0xbffff860</code></p><p class="book"><code>&nbsp;End of alloca()'ed array: 0xbffff87f</code></p><p class="book"><code>&nbsp; /* Адреса находятся в стеке */</code></p><p class="book"><code>Data Locations:</code></p><p class="book"><code>&nbsp;Address of data_var: 0x80496b8</code></p><p class="book"><code>BSS Locations:</code></p><p class="book"><code>&nbsp;Address of bss_var: 0x80497c4</code></p><p class="book"><code>&nbsp; /* BSS выше инициализированных данных */</code></p><p class="book"><code>Heap Locations:</code></p><p class="book"><code>&nbsp;Initial end of heap: 0x80497c8</code></p><p class="book"><code>&nbsp; /* Куча непосредственно над BSS */</code></p><p class="book"><code>&nbsp;New end of heap: 0x80497e8</code></p><p class="book"><code>&nbsp; /* И растет вверх */</code></p><p class="book"><code>&nbsp;Final end of heap: 0x80497d8</code></p><p class="book"><code>&nbsp; /* Адресные пространства можно сокращать */</code></p><a name="t59"></a><h3 class="book">3.3. Резюме<br> </h3> <p class="book">•&nbsp;У каждой программы Linux и (Unix) есть различные области памяти. Они хранятся в разных частях файла исполняемой программы на диске. Некоторые из секций загружаются при запуске программы в одну и ту же область памяти. Все запушенные экземпляры одной и той же программы разделяют исполняемый код (сегмент текста). Программа <code>size</code> показывает размеры различных областей переместимых объектных файлов и полностью скомпонованных исполняемых файлов.</p> <p class="book">•&nbsp;В адресном пространстве запушенной программы могут быть дыры, а размер адресного пространства может изменяться при выделении и освобождении памяти. На современных системах адрес 0 не является частью адресного пространства, поэтому не пытайтесь разыменовывать указатели <code>NULL</code>.</p> <p class="book">•&nbsp;На уровне языка С память выделяется с помощью одной из функций <code>malloc()</code>, <code>calloc()</code> или <code>realloc()</code>. Память освобождается с помощью <code>free()</code>. (Хотя с помощью <code>realloc()</code> можно делать все, использование ее таким образом не рекомендуется.) Освобожденная память обычно не удаляется из адресного пространства; вместо этого она используется повторно при последующих выделениях.</p> <p class="book">•&nbsp;Необходимо предпринять чрезвычайные меры осторожности в следующих случаях</p> <p class="book">&nbsp;•&nbsp;освобождать лишь память, выделенную с помощью соответствующих процедур,</p> <p class="book">&nbsp;•&nbsp;освобождать память один и только один раз,</p> <p class="book">&nbsp;•&nbsp;освобождать неиспользуемую память и</p> <p class="book">&nbsp;•&nbsp;не допускать «утечки» динамически выделяемой памяти.</p> <p class="book">• POSIX предоставляет для удобства функцию <code>strdup()</code>, a GLIBC предоставляет функции <code>getline()</code> и <code>getdelim()</code> для чтения строк произвольной длины. Функции интерфейса низкоуровневых системных вызовов <code>brk()</code> и <code>sbrk()</code> предоставляют непосредственный, но примитивный доступ к выделению и освобождению памяти. Если вы не создаете свой собственный распределитель памяти, следует избегать их. Существует функция <code>alloca()</code> для выделения памяти в стеке, но ее использование не рекомендуется. Подобно умению распознавать ядовитый плющ, про нее нужно знать лишь для того, чтобы избегать ее.</p> <a name="t60"></a><h3 class="book">Упражнения<br> </h3> <p class="book">1.&nbsp;Начав со структуры —</p> <p class="book"><code>struct line {</code></p> <p class="book"><code>&nbsp;size_t buflen;</code></p> <p class="book"><code>&nbsp;char *buf;</code></p> <p class="book"><code>&nbsp;FILE* fp;</code></p> <p class="book"><code>};</code></p> <p class="book">—&nbsp;напишите свою собственную функцию <code>readline()</code>, которая будет читать строки любой длины. Не беспокойтесь о строках, продолженных с помощью обратного слеша. Вместо использования <code>fgetc()</code> для чтения строк используйте <code>getc()</code> для чтения одного символа за раз.</p> <p class="book">2.&nbsp;Сохраняет ли ваша функция завершающий символ конца строки? Объясните, почему.</p> <p class="book">3.&nbsp;Как ваша функция обрабатывает строки, оканчивающиеся CR-LF?</p> <p class="book">4.&nbsp;Как вы инициализируете структуру? В отдельной процедуре? С помощью документированных условий для определенных значений в структуре?</p> <p class="book">5.&nbsp;Как вы обозначаете конец файла? Как вы указываете, что возникла ошибка ввода/вывода? Должна ли ваша функция сообщать об ошибках? Объясните, почему.</p> <p class="book">6.&nbsp;Напишите программу, которая использует вашу функцию для ее тестирования, а также другую программу, создающую входные данные для первой программы. Протестируйте функцию.</p> <p class="book">7.&nbsp;Перепишите вашу функцию с использованием <code>fgets()</code> и протестируйте ее. Является ли новый код более сложным или менее сложным? Какова его производительность по сравнению с версией <code>getc()</code>?</p> <p class="book">8.&nbsp;Изучите страницу справки V7 для <i>end</i>(3) (<code>/usr/man/man3/end.3</code> в дистрибутиве V7). Пролила ли она свет на то, как может работать '<code>sbrk(0)</code>'?</p> <p class="book">9.&nbsp;Усовершенствуйте <code>ch03-memaddr.c</code> так, чтобы она печатала расположение аргументов и переменных окружения. В какой области адресного пространства они находятся?</p><a name="t61"></a><h3 class="book"> Глава 4<br> Файлы и файловый ввод/вывод<br></h3> <p class="book">Данная глава описывает базовые файловые операции: открытие и создание файлов, чтение и запись в них, перемещение в них и их закрытие. По ходу дела она представляет стандартные механизмы для обнаружения ошибок и сообщений о них. Глава заканчивается описанием того, как установить длину файла и принудительно сбросить данные файла и вспомогательные данные на диск.</p> <a name="t62"></a><h3 class="book">4.1. Введение в модель ввода/вывода Linux/Unix<br> </h3> <p class="book">Модель API Linux/Unix для ввода/вывода проста. Ее можно суммировать четырьмя словами. открыть, прочитать, записать, закрыть. Фактически, это имена системных вызовов: <code>open()</code>, <code>read()</code>, <code>write()</code>, <code>close()</code>. Вот их объявления:</p> <p class="book"><code>#include &lt;sys/types.h&gt; /* POSIX */</code></p> <p class="book"><code>#include &lt;sys/stat.h&gt; /* для mode_t */</code></p> <p class="book"><code>#include &lt;fcntl.h&gt;&nbsp;&nbsp; &nbsp;/* для flags для open() */</code></p> <p class="book"><code>#include &lt;unistd.h&gt;&nbsp;&nbsp; /* для ssize_t */</code></p> <br> <p class="book"><code>int open(const char *pathname, int flags, mode_t mode);</code></p> <p class="book"><code>ssize_t read(int fd, void *buf, size_t count);</code></p> <p class="book"><code>ssize_t write(int fd, const void *buf, size_t count);</code></p> <p class="book"><code>int close(int fd);</code></p> <p class="book">В следующем и дальнейших разделах мы проиллюстрируем модель, написав очень простую версию <code>cat</code>. Она так проста, что даже не имеет опций; все, что она делает, — объединяет содержимое двух именованных файлов и выводит его в стандартный вывод. Она выводит минимум сообщений об ошибках. Написав, мы сравним ее с V7 <code>cat</code>.</p> <p class="book">Мы представим программу сверху вниз, начав с командной строки. В последующих разделах мы представим обработку ошибок, а затем перейдем к сущностным задачам, показав, каким образом осуществляется реальный файловый ввод/вывод.</p> <a name="t63"></a><h3 class="book">4.2. Представление базовой структуры программы<br> </h3> <p class="book">Наша версия cat следует структуре, которая обычно является полезной. Первая часть начинается с комментариев, заголовочных файлов, объявлений и функции main():</p> <p class="book"><code>1&nbsp; /*</code></p> <p class="book"><code>2&nbsp;&nbsp; * ch04-cat.c --- Демонстрация open(), read(), write(), close(),</code></p> <p class="book"><code>3&nbsp;&nbsp;&nbsp;* errno и strerror().</code></p> <p class="book"><code>4&nbsp;&nbsp; */</code></p> <p class="book"><code>5</code></p> <p class="book"><code>6&nbsp; #include &lt;stdio.h&gt; /* для fprintf(), stderr, BUFSIZ */</code></p> <p class="book"><code>7&nbsp; #include &lt;errno.h&gt; /* объявление errno */</code></p> <p class="book"><code>8&nbsp; #include &lt;fcntl.h&gt; /* для flags для open() */</code></p> <p class="book"><code>9 &nbsp;#include &lt;string.h&gt; /* объявление strerror() */</code></p> <p class="book"><code>10 #include &lt;unistd.h&gt; /* для ssize_t */</code></p> <p class="book"><code>11 #include &lt;sys/types.h&gt;</code></p> <p class="book"><code>12 #include &lt;sys/stat.h&gt; /* для mode_t */</code></p> <p class="book"><code>13</code></p> <p class="book"><code>14 char *myname;</code></p> <p class="book"><code>15 int process(char *file);</code></p> <p class="book"><code>16</code></p> <p class="book"><code>17 /* main --- перечислить аргументы файла */</code></p> <p class="book"><code>18</code></p> <p class="book"><code>19 int</code></p> <p class="book"><code>20 main(int argc, char **argv)</code></p> <p class="book"><code>21 {</code></p> <p class="book"><code>22&nbsp; int i;</code></p> <p class="book"><code>23&nbsp; int errs = 0;</code></p> <p class="book"><code>24</code></p> <p class="book"><code>25&nbsp; myname = argv[0];</code></p> <p class="book"><code>26</code></p> <p class="book"><code>27&nbsp; if (argc == 1)</code></p> <p class="book"><code>28&nbsp;&nbsp; errs = process("-");</code></p> <p class="book"><code>29&nbsp; else</code></p> <p class="book"><code>30&nbsp;&nbsp; for (i = 1; i &lt; argc; i++)</code></p> <p class="book"><code>31&nbsp; errs += process(argv[i]);</code></p> <p class="book"><code>32</code></p> <p class="book"><code>33&nbsp; return (errs != 0);</code></p> <p class="book"><code>34 }</code></p> <p class="book"><i>…продолжение далее в главе.</i></p> <p class="book">Переменная <code>myname</code> (строка 14) используется далее для сообщений об ошибках; <code>main()</code> первым делом устанавливает в ней имя программы (<code>argv[0]</code>). Затем <code>main()</code> в цикле перечисляет аргументы. Для каждого аргумента она вызывает функцию <code>process()</code>.</p> <p class="book">Когда в качестве имени файла дано <code>-</code> (простая черточка, или знак минус), <code>cat</code> Unix вместо попытки открыть файл с именем читает стандартный ввод. Вдобавок, <code>cat</code> читает стандартный ввод, когда нет аргументов. <code>ch04-cat</code> реализует оба этих поведения. Условие '<code>argc == 1</code>' (строка 27) истинно, когда нет аргументов имени файла; в этом случае <code>main()</code> передает «<code>-</code>» функции <code>process()</code>. В противном случае, <code>main()</code> перечисляет аргументы, рассматривая их как файлы, которые необходимо обработать. Если один из них окажется «<code>-</code>», программа обрабатывает стандартный ввод.</p> <p class="book">Если <code>process()</code> возвращает ненулевое значение, это означает, что случилась какая- то ошибка. Ошибки подсчитываются в переменной <code>errs</code> (строки 28 и 31). Когда <code>main()</code> завершается, она возвращает 0, если не было ошибок, и 1, если были (строка 33). Это довольно стандартное соглашение, значение которого более подробно обсуждается в разделе 9.1.5.1 «Определение статуса завершения процесса».</p> <p class="book">Структура, представленная в <code>main()</code>, довольно общая: <code>process()</code> может делать с файлом все, что мы захотим. Например (игнорируя особый случай «<code>-</code>»), process() также легко могла бы удалять файлы вместо их объединения!</p> <p class="book">Прежде чем рассмотреть функцию <code>process()</code>, нам нужно описать, как представлены ошибки системных вызовов и как осуществляется ввод/вывод. Сама функция <code>process()</code> представлена в разделе 4.4.3 «Чтение и запись».</p> <a name="t64"></a><h3 class="book">4.3. Определение ошибок<br> </h3> <blockquote class="epigraph"><p class="book">«Если неприятность может произойти, она случается»</p><blockquote class="book"><i>- Закон Мерфи -</i></blockquote> </blockquote> <blockquote class="epigraph"><p class="book">«Будь готов»</p><blockquote class="book"><i>- Бойскауты -</i></blockquote> </blockquote> <p class="book">Ошибки могут возникнуть в любое время. Диски могут заполниться, пользователи могут ввести неверные данные, сетевой сервер, с которого осуществляется чтение, может отказать, сеть может выйти из строя и т.д. Важно <i>всегда</i> проверять успешность завершения каждой операции.</p><p class="book">Основные системные вызовы Linux почти всегда возвращают при ошибке -1 и 0 или положительное значение при успехе. Это дает возможность узнать, была операция успешной или нет:</p><p class="book"><code>int result;</code></p><p class="book"><code>result = some_system_call(param1, param2);</code></p><p class="book"><code>if (result &lt; 0) {</code></p><p class="book"><code>&nbsp;/* ошибка, что-нибудь сделать */</code></p><p class="book"><code>} else</code></p><p class="book"><code>&nbsp;/* все нормально, продолжить */</code></p><p class="book">Знания того, что произошла ошибка, недостаточно. Нужно знать, <i>какая</i> произошла ошибка. Для этого у каждого процесса есть предопределенная переменная с именем <code>errno</code>. Всякий раз, когда системный вызов завершается ошибкой, <code>errno</code> устанавливается в один из набора предопределенных значений ошибок <code>errno</code> и предопределенные значения ошибок определены в файле заголовка <code>&lt;errno.h&gt;</code>.</p><p class="book"><code>#include &lt;errno.h&gt; /* ISO С */</code></p><br><p class="book"><code>extern int errno;</code></p><p class="book">Хотя сама <code>errno</code> может быть макросом, который <i>действует подобно</i> переменной <code>int</code>&nbsp;— она не обязательно является действительной целой переменной. В частности, в многопоточном окружении у каждого потока будет своя индиви</p><div class="btn btn-read"><a class="coolbtn btn-color" href="https://www.litres.ru/pages/rmd_search/?q=Linux программирование в примерах&amp;lfrom=700971545" target="_blank">Продолжить чтение книги</a></div></div>